Complete guide to Python's os.wait function covering child process management, process synchronization, and practical examples.
Last modified April 11, 2025
This comprehensive guide explores Python’s os.wait function, which waits for child process completion. We’ll cover process management, exit status interpretation, and practical synchronization examples.
The os.wait function suspends execution until a child process terminates. It returns a tuple containing the process ID and exit status.
Key features: blocks until child process exits, reaps zombie processes, returns (pid, status) tuple. Only works with child processes of the caller.
This example demonstrates the simplest use of os.wait to wait for a single child process to complete. The parent forks a child that sleeps.
basic_wait.py
import os import time
pid = os.fork()
if pid == 0: # Child process print(“Child process started”) time.sleep(2) print(“Child process exiting”) os._exit(0) else: # Parent process print(f"Parent waiting for child {pid}") child_pid, status = os.wait() print(f"Child {child_pid} exited with status {status}")
The parent process forks a child that sleeps for 2 seconds. The parent calls os.wait() which blocks until the child terminates. The exit status is printed.
Note that os._exit() is used in the child to ensure proper process termination without running Python’s cleanup handlers.
This example shows how to wait for multiple child processes using a loop. The parent creates several children and waits for each to complete.
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 {os.getpid()} sleeping for {sleep_time}s") time.sleep(sleep_time) os._exit(0) else: # Parent children.append(pid)
while children: pid, status = os.wait() print(f"Child {pid} exited with status {status}") children.remove(pid)
print(“All children have exited”)
The parent creates 3 child processes with random sleep durations. It maintains a list of child PIDs and waits for each one in sequence using os.wait().
The order of completion depends on the sleep times. os.wait() returns for any terminated child, not necessarily in creation order.
The WNOHANG option makes os.waitpid non-blocking, allowing polling for child process completion. This example demonstrates periodic status checking.
non_blocking.py
import os import time
pid = os.fork()
if pid == 0: # Child print(“Child running”) time.sleep(3) print(“Child exiting”) os._exit(42) else: # Parent print(f"Parent waiting for child {pid}") while True: try: child_pid, status = os.waitpid(pid, os.WNOHANG) if child_pid == 0: print(“Child still running…”) time.sleep(1) else: print(f"Child exited with status {status}") break except ChildProcessError: print(“No such child process”) break
The parent periodically checks the child’s status using os.waitpid with WNOHANG. If the child hasn’t exited, waitpid returns (0, 0) immediately.
This pattern is useful when the parent needs to perform other work while monitoring child processes without blocking.
The exit status returned by os.wait contains encoded information. This example shows how to interpret it using os.WIFEXITED and related functions.
exit_status.py
import os import sys
pid = os.fork()
if pid == 0: # Child print(“Child running”) # Exit with different statuses if len(sys.argv) > 1: os._exit(int(sys.argv[1])) else: os._exit(0) else: # Parent pid, status = os.wait() print(f"Raw status: {status}")
if os.WIFEXITED(status):
print(f"Child exited normally with status {os.WEXITSTATUS(status)}")
elif os.WIFSIGNALED(status):
print(f"Child killed by signal {os.WTERMSIG(status)}")
elif os.WIFSTOPPED(status):
print(f"Child stopped by signal {os.WSTOPSIG(status)}")
The child exits with either a status provided as argument or 0. The parent interprets the status using os.WIF* functions to determine how the child exited.
These macros help distinguish normal exits from terminations by signals or process stops, providing detailed process termination information.
os.waitpid allows waiting for a specific child process rather than any child. This example demonstrates targeted process waiting.
specific_process.py
import os import time
child1 = os.fork() if child1 == 0: time.sleep(1) os._exit(10)
child2 = os.fork() if child2 == 0: time.sleep(2) os._exit(20)
print(f"Waiting for child {child2}") pid, status = os.waitpid(child2, 0) print(f"Child {pid} exited with status {status}")
pid, status = os.wait() print(f"Child {pid} exited with status {status}")
The parent creates two children with different sleep durations. It first waits specifically for child2 using waitpid, then waits for any remaining child.
This approach is useful when you need to manage multiple children with different priorities or dependencies between them.
This example shows how to handle cases where child processes might become orphaned. The parent process terminates before waiting for its children.
orphaned_process.py
import os import time import sys
pid = os.fork()
if pid == 0: # Child print(“Child running”) time.sleep(5) print(“Child exiting”) os._exit(0) else: # Parent print(f"Parent created child {pid}") if len(sys.argv) > 1 and sys.argv[1] == “wait”: pid, status = os.wait() print(f"Parent saw child exit with status {status}") else: print(“Parent exiting without waiting”) sys.exit(0)
When run without arguments, the parent exits immediately, orphaning the child. With “wait” argument, the parent waits properly. Observe process hierarchy.
Orphaned children are adopted by init (PID 1) which will eventually reap them. Proper process management prevents zombie processes.
This example demonstrates proper error handling when using os.wait, including cases with no child processes or interrupted system calls.
error_handling.py
import os import time import errno
try: pid, status = os.wait() print(f"Child {pid} exited with status {status}") except ChildProcessError: print(“No child processes to wait for”)
pid = os.fork() if pid == 0: time.sleep(3) os._exit(0) else: try: # Simulate interrupt (in real code this might happen naturally) import signal signal.alarm(1) # SIGALRM in 1 second pid, status = os.wait() except InterruptedError: print(“Wait was interrupted”) # Typically you would retry the wait here pid, status = os.wait() print(f"Child {pid} exited with status {status}")
The first case shows handling when no children exist. The second demonstrates interruption handling, common with signal handlers. Proper error recovery.
Always handle potential errors when working with process management to avoid unexpected behavior or resource leaks in your application.
Zombie processes: Always wait for children to prevent zombies
Signal safety: Be aware wait can be interrupted by signals
Privileges: Child processes inherit parent’s privileges
Resource limits: Too many children may hit system limits
Platform differences: Behavior may vary between Unix systems
Always wait: Prevent zombie process accumulation
Check status: Interpret exit status properly
Handle errors: Account for ECHILD and EINTR
Consider alternatives: subprocess module may be simpler
Document behavior: Note process management in your API
My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.
List all Python tutorials.