ZetCode

Python os.wait3 函数

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

本综合指南探讨 Python 的 os.wait3 函数,该函数等待子进程完成并返回资源使用情况数据。我们将介绍进程管理、状态解释和实践示例。

基本定义

os.wait3 函数等待任何子进程完成,并返回其进程 ID、退出状态和资源使用信息。

主要参数:options (WNOHANG, WUNTRACED)。返回 (pid, status, resource_usage) 的元组。仅在类 Unix 系统上可用。

基本进程等待

os.wait3 的最简单用法是等待任何子进程终止。此示例创建一个子进程并等待其完成。

basic_wait.py
import os
import sys

pid = os.fork()

if pid == 0:  # Child process
    print("Child process running")
    sys.exit(42)
else:  # Parent process
    print(f"Parent waiting for child {pid}")
    pid, status, rusage = os.wait3(0)
    print(f"Child {pid} exited with status {status}")
    print(f"Exit code: {os.WEXITSTATUS(status)}")
    print(f"Resource usage: {rusage}")

此示例 fork 一个退出代码为 42 的子进程。父进程等待子进程并打印其退出状态和资源使用信息。

os.WEXITSTATUS 宏从 wait3 返回的状态值中提取退出代码。资源使用情况包括 CPU 时间和内存统计信息。

使用 WNOHANG 进行非阻塞等待

WNOHANG 选项使 os.wait3 成为非阻塞的。如果没有子进程终止,它会立即返回。这允许轮询。

non_blocking.py
import os
import time

pid = os.fork()

if pid == 0:  # Child
    print("Child sleeping for 2 seconds")
    time.sleep(2)
    os._exit(0)
else:  # Parent
    print("Parent polling for child completion")
    while True:
        result = os.wait3(os.WNOHANG)
        if result[0] != 0:  # Child exited
            print(f"Child {result[0]} exited")
            break
        print("Child still running...")
        time.sleep(0.5)

父进程使用 WNOHANG 每 0.5 秒轮询一次子进程完成情况。这可以防止在等待子进程完成睡眠时发生阻塞。

当没有子进程退出时,wait3 返回 (0, 0, 0)。仅当子进程终止时才返回实际的子进程 PID。

处理多个子进程

os.wait3 可以管理多个子进程。此示例创建多个子进程,并在它们终止时按顺序等待它们。

multiple_children.py
import os
import time
import random

children = []
for i in range(3):
    pid = os.fork()
    if pid == 0:  # Child
        sleep_time = random.randint(1, 3)
        print(f"Child {i} sleeping for {sleep_time}s")
        time.sleep(sleep_time)
        os._exit(i)
    else:  # Parent
        children.append(pid)

print(f"Parent waiting for children {children}")
while children:
    pid, status, _ = os.wait3(0)
    exit_code = os.WEXITSTATUS(status)
    print(f"Child {pid} exited with code {exit_code}")
    children.remove(pid)

创建了三个具有随机睡眠持续时间的子进程。父进程按照它们完成的顺序等待每个子进程,而不是按照它们创建的顺序。

children 列表跟踪活动的子进程 PID。 随着每个子进程退出,它会从列表中删除,直到所有子进程都被收回。

捕获资源使用情况

os.wait3 中的资源使用情况元组提供了有关 CPU 时间、内存使用情况和其他系统资源的详细信息。

resource_usage.py
import os
import time

def child_work():
    # Simulate CPU and memory usage
    start = time.time()
    data = []
    while time.time() - start < 1:  # Run for 1 second
        data.append("x" * 1000)  # Allocate memory
    return len(data)

pid = os.fork()

if pid == 0:  # Child
    count = child_work()
    os._exit(count)
else:  # Parent
    pid, status, rusage = os.wait3(0)
    print(f"Child user time: {rusage.ru_utime:.3f}s")
    print(f"Child system time: {rusage.ru_stime:.3f}s")
    print(f"Max RSS: {rusage.ru_maxrss} KB")
    print(f"Page faults: {rusage.ru_majflt}")
    print(f"Blocks in: {rusage.ru_inblock}")
    print(f"Blocks out: {rusage.ru_oublock}")

子进程执行 CPU 密集型工作和内存分配。 父进程在子进程退出后捕获详细的资源使用情况统计信息。

主要指标包括用户 CPU 时间、系统 CPU 时间、最大常驻集大小、页面错误和块 I/O 操作。

处理停止的进程

使用 WUNTRACED 选项,os.wait3 可以检测子进程何时被停止(例如,通过 SIGSTOP),而不是被终止。

stopped_process.py
import os
import signal
import time

pid = os.fork()

if pid == 0:  # Child
    print("Child running, will be stopped")
    time.sleep(10)  # Give time to send signal
    os._exit(0)
else:  # Parent
    print(f"Parent waiting for child {pid}")
    time.sleep(1)  # Wait a moment before stopping
    os.kill(pid, signal.SIGSTOP)
    
    pid, status, _ = os.wait3(os.WUNTRACED)
    if os.WIFSTOPPED(status):
        print(f"Child {pid} stopped by signal {os.WSTOPSIG(status)}")
    
    # Continue the child
    os.kill(pid, signal.SIGCONT)
    pid, status, _ = os.wait3(0)
    print(f"Child {pid} exited with status {status}")

父进程向子进程发送 SIGSTOP,然后使用 WUNTRACED 检测停止状态。 然后,它使用 SIGCONT 继续子进程,并等待其终止。

os.WIFSTOPPED 检查子进程是否已停止,而 os.WSTOPSIG 获取停止信号编号。

错误处理

os.wait3 在某些情况下可能会引发 OSError。 此示例演示了等待子进程时的正确错误处理。

error_handling.py
import os
import errno

try:
    # Try to wait when no children exist
    pid, status, rusage = os.wait3(os.WNOHANG)
    print("This won't be reached")
except OSError as e:
    if e.errno == errno.ECHILD:
        print("No child processes to wait for")
    else:
        print(f"Unexpected error: {e}")

# Create and immediately wait for child
pid = os.fork()
if pid == 0:
    os._exit(0)
else:
    try:
        pid, status, rusage = os.wait3(0)
        print(f"Child {pid} exited normally")
    except OSError as e:
        print(f"Error waiting for child: {e}")

第一次尝试等待失败,并出现 ECHILD(没有子进程)。 第二部分显示了创建子进程后成功等待的情况。

正确的错误处理至关重要,因为系统条件可能会在进程创建和等待操作之间发生变化。

高级进程组

os.wait3 可以监视特定进程组中的进程。 此示例演示了管理子进程的进程组。

process_groups.py
import os
import time

# Create new process group
os.setpgrp()

children = []
for i in range(3):
    pid = os.fork()
    if pid == 0:  # Child
        # Children in same process group
        print(f"Child {i} in group {os.getpgrp()}")
        time.sleep(i + 1)
        os._exit(0)
    else:
        children.append(pid)

print(f"Parent waiting for children in group {os.getpgrp()}")
while children:
    try:
        pid, status, _ = os.wait3(0)
        if pid > 0:
            print(f"Child {pid} exited")
            children.remove(pid)
    except OSError as e:
        if e.errno == errno.ECHILD:
            break
        raise

print("All children exited")

这会创建一个新的进程组和几个子进程。 父进程等待其进程组中的所有子进程退出,并适当处理错误。

进程组允许一起管理相关进程,并且对于 shell 作业控制实现特别有用。

安全注意事项

最佳实践

资料来源

作者

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

列出所有 Python 教程