ZetCode

Python os.waitid 函数

上次修改时间:2025 年 4 月 11 日

本全面指南探讨 Python 的 os.waitid 函数,该函数提供对等待子进程状态更改的详细控制。我们将介绍进程选择、状态检索和实际示例。

基本定义

os.waitid 函数等待子进程更改状态。 它比 os.waitos.waitpid 提供更多的控制。

关键参数:idtype (P_PID, P_PGID, P_ALL),id (进程 ID/组 ID),options (WNOHANG, WNOWAIT 等),返回带有详细信息的 siginfo_t 结构。

等待特定的子进程

此示例演示了等待特定子进程终止。 我们使用 P_PID 作为 idtype 来定位特定的进程 ID。

wait_specific.py
import os
import time

pid = os.fork()

if pid == 0:
    # Child process
    print("Child process running")
    time.sleep(2)
    print("Child process exiting")
    os._exit(42)
else:
    # Parent process
    print(f"Parent waiting for child PID {pid}")
    siginfo = os.waitid(os.P_PID, pid, os.WEXITED)
    print(f"Child exited with status {siginfo.si_status}")

父进程创建一个休眠 2 秒钟然后退出的子进程。 父进程专门等待此子进程,使用其 PID。

WEXITED 选项表示我们要等待终止事件。 siginfo_t 结构包含 si_status 中的退出状态。

非阻塞进程等待

使用 WNOHANG 选项允许对子进程状态进行非阻塞检查。 当您需要轮询状态同时执行其他工作时,这非常有用。

non_blocking.py
import os
import time

pid = os.fork()

if pid == 0:
    # Child process
    time.sleep(3)
    os._exit(0)

# Parent process
while True:
    try:
        siginfo = os.waitid(os.P_PID, pid, os.WEXITED | os.WNOHANG)
        print(f"Child exited with status {siginfo.si_status}")
        break
    except ChildProcessError:
        print("Child still running, doing other work...")
        time.sleep(1)

父进程定期检查子进程的状态而不阻塞。 循环持续进行,直到子进程退出并且 waitid 成功返回。

当设置 WNOHANG 并且没有子进程匹配时,会引发 ChildProcessError。 这是非阻塞轮询的正常行为。

等待进程组

使用 P_PGID 作为 idtype 允许等待组中的任何进程。 当管理多个相关的子进程时,这非常有用。

process_group.py
import os
import time

# Create process group
pgid = os.getpid()
os.setpgid(0, pgid)

# Create child processes
for i in range(3):
    pid = os.fork()
    if pid == 0:
        os.setpgid(0, pgid)
        print(f"Child {i} (PID {os.getpid()}) running")
        time.sleep(i + 1)
        print(f"Child {i} exiting")
        os._exit(i)
        break

# Parent waits for any process in group
if pid != 0:
    print(f"Waiting for processes in group {pgid}")
    siginfo = os.waitid(os.P_PGID, pgid, os.WEXITED)
    print(f"Process {siginfo.si_pid} exited with status {siginfo.si_status}")

父进程创建一个进程组并生成三个子进程。 然后它等待组中的任何进程终止。

请注意,每次 waitid 调用仅报告一个进程。 需要多次调用才能等待组中的所有子进程。

获取扩展的进程信息

os.waitid 在 siginfo_t 结构中提供详细的进程信息。 此示例显示了如何访问此结构的各个字段。

extended_info.py
import os
import signal

pid = os.fork()

if pid == 0:
    # Child process
    print("Child running")
    time.sleep(2)
    os._exit(127)
else:
    # Parent process
    siginfo = os.waitid(os.P_PID, pid, os.WEXITED)
    print(f"Process {siginfo.si_pid} status:")
    print(f"  Exit status: {siginfo.si_status}")
    print(f"  Signal: {siginfo.si_signo}")
    print(f"  Code: {siginfo.si_code}")
    print(f"  UID: {siginfo.si_uid}")
    print(f"  Timestamp: {siginfo.si_stime}")

siginfo_t 结构包含有关该进程的广泛信息。 这包括退出状态、信号编号、用户 ID 和时间戳。

不同的字段与进程终止的方式有关。 对于正常退出,si_status 包含退出代码。

等待而不消耗状态

WNOWAIT 选项将子进程在检索后保留在可等待状态。 这允许其他等待调用也获取子进程的状态信息。

nowait.py
import os

pid = os.fork()

if pid == 0:
    # Child process
    os._exit(99)

# First wait with WNOWAIT
siginfo1 = os.waitid(os.P_PID, pid, os.WEXITED | os.WNOWAIT)
print(f"First wait: status {siginfo1.si_status}")

# Second wait without WNOWAIT
siginfo2 = os.waitid(os.P_PID, pid, os.WEXITED)
print(f"Second wait: status {siginfo2.si_status}")

try:
    # Third attempt should fail
    os.waitid(os.P_PID, pid, os.WEXITED)
except ChildProcessError:
    print("No more status available")

第一次等待使用 WNOWAIT 来查看状态而不消耗它。 第二次等待正常检索状态,消耗它。

第三次尝试失败,因为该状态已被第二次等待消耗。 这演示了 WNOWAIT 的效果。

处理已停止/继续的进程

os.waitid 可以检测到何时使用信号停止或继续进程。 这需要使用 WSTOPPED 或 WCONTINUED 作为选项。

stopped_process.py
import os
import signal
import time

pid = os.fork()

if pid == 0:
    # Child process
    while True:
        print("Child running")
        time.sleep(1)
else:
    # Parent process
    time.sleep(1)
    os.kill(pid, signal.SIGSTOP)
    print("Sent SIGSTOP to child")
    
    siginfo = os.waitid(os.P_PID, pid, os.WSTOPPED)
    print(f"Child stopped by signal {siginfo.si_status}")
    
    os.kill(pid, signal.SIGCONT)
    print("Sent SIGCONT to child")
    
    siginfo = os.waitid(os.P_PID, pid, os.WCONTINUED)
    print("Child continued")
    
    os.kill(pid, signal.SIGTERM)
    siginfo = os.waitid(os.P_PID, pid, os.WEXITED)
    print("Child terminated")

父进程使用 SIGSTOP 停止子进程,等待停止事件,然后使用 SIGCONT 继续它,最后终止它。

每个状态更改都使用适当的等待选项进行检测。 WSTOPPED 捕获停止,WCONTINUED 捕获恢复,WEXITED 捕获退出。

等待任何子进程

使用 P_ALL 作为 idtype 允许等待任何子进程。 这类似于 os.wait,但具有更详细的信息。

wait_any.py
import os
import time

# Create multiple children
for i in range(3):
    pid = os.fork()
    if pid == 0:
        print(f"Child {i} running")
        time.sleep(i + 1)
        os._exit(i + 10)
        break

# Parent waits for any child
if pid != 0:
    for _ in range(3):
        siginfo = os.waitid(os.P_ALL, 0, os.WEXITED)
        print(f"Child {siginfo.si_pid} exited with status {siginfo.si_status}")

父进程创建三个具有不同生存期的子进程。 然后它使用 P_ALL 作为 idtype 依次等待每个子进程。

父进程进行三次 waitid 调用以捕获所有子进程退出。 报告的子进程的顺序取决于它们的终止顺序。

安全注意事项

最佳实践

资料来源

作者

我的名字是 Jan Bodnar,我是一位充满激情的程序员,拥有丰富的编程经验。 自 2007 年以来,我一直在撰写编程文章。 迄今为止,我已经撰写了超过 1,400 篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有 Python 教程