Complete guide to Python's os.waitpid function covering process management, child process synchronization, and practical examples.
Last modified April 11, 2025
This comprehensive guide explores Python’s os.waitpid function, which waits for a child process to complete and retrieves its exit status. We’ll cover process management, status interpretation, and practical examples.
The os.waitpid function waits for a child process specified by process ID (pid) to complete and returns its exit status information.
Key parameters: pid (process ID to wait for), options (flags like WNOHANG). Returns a tuple of (pid, status). Status contains exit code and signal info.
This example demonstrates the simplest use of os.waitpid to wait for a child process to complete. The parent process waits indefinitely.
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}") child_pid, status = os.waitpid(pid, 0) print(f"Child {child_pid} exited with status {status}") if os.WIFEXITED(status): print(f"Exit code: {os.WEXITSTATUS(status)}")
The parent process forks a child that exits with code 42. The parent waits for the child to complete using waitpid. WIFEXITED checks for normal exit.
The status contains more information than just the exit code, including signal information if the process was terminated by a signal.
The WNOHANG option makes os.waitpid non-blocking, returning immediately if the child hasn’t exited yet. This allows polling.
non_blocking.py
import os import time
pid = os.fork()
if pid == 0: # Child process runs for 3 seconds time.sleep(3) os._exit(0)
while True: child_pid, status = os.waitpid(pid, os.WNOHANG) if child_pid == 0: print(“Child still running…”) time.sleep(1) else: print(f"Child {child_pid} exited") break
The parent checks the child status every second without blocking. When the child exits after 3 seconds, waitpid returns the child’s PID and status.
This pattern is useful when you need to monitor a process while continuing to perform other tasks in the parent process.
Using pid of -1 waits for any child process. This is useful when managing multiple children and you don’t know which will complete first.
wait_any.py
import os import time import random
children = [] for i in range(3): pid = os.fork() if pid == 0: # Child sleeps for random time and exits sleep_time = random.randint(1, 5) time.sleep(sleep_time) os._exit(sleep_time) else: children.append(pid)
while children: pid, status = os.waitpid(-1, 0) if os.WIFEXITED(status): print(f"Child {pid} exited after {os.WEXITSTATUS(status)} seconds") children.remove(pid)
print(“All children exited”)
The parent creates 3 children that sleep for random durations. Using pid=-1, it waits for whichever child exits first, removing it from the tracking list.
This approach is common in servers that handle multiple client processes concurrently.
This example shows how to handle children terminated by signals using the status information returned by os.waitpid.
signal_handling.py
import os import signal import time
pid = os.fork()
if pid == 0: # Child process waits to be killed print(“Child running (PID: {})".format(os.getpid())) while True: time.sleep(1) else: # Parent waits briefly then kills child time.sleep(2) os.kill(pid, signal.SIGTERM)
# Wait for child and check status
child_pid, status = os.waitpid(pid, 0)
if os.WIFSIGNALED(status):
sig = os.WTERMSIG(status)
print(f"Child {child_pid} killed by signal {sig} ({signal.Signals(sig).name})")
elif os.WIFEXITED(status):
print(f"Child exited normally with status {os.WEXITSTATUS(status)}")
The parent sends SIGTERM to the child after 2 seconds. waitpid returns status information that we decode using WIFSIGNALED and WTERMSIG.
This demonstrates how to distinguish between normal exits and signal-induced terminations when managing child processes.
Using a pid less than -1 waits for any child in the specified process group. This is useful for managing groups of related processes.
process_group.py
import os import time
os.setpgrp()
for i in range(3): pid = os.fork() if pid == 0: # Children sleep and exit time.sleep(i + 1) os._exit(0)
group_pid = -os.getpgrp() exited = 0
while exited < 3: child_pid, status = os.waitpid(group_pid, 0) exited += 1 print(f"Child {child_pid} exited (total exited: {exited})”)
The parent creates a new process group and spawns 3 children. Using a negative pid equal to the negative of the group ID, it waits for any child in the group.
This technique is particularly useful when you want to manage all descendants of a process together.
This example demonstrates comprehensive status interpretation using all the available macros for examining the waitpid status value.
status_interpretation.py
import os import signal import random
def analyze_status(pid, status): print(f"\nAnalysis for process {pid}:") if os.WIFEXITED(status): print(f" Exited normally with status {os.WEXITSTATUS(status)}") if os.WIFSIGNALED(status): print(f" Killed by signal {os.WTERMSIG(status)}") print(f" Core dumped: {os.WCOREDUMP(status)}") if os.WIFSTOPPED(status): print(f" Stopped by signal {os.WSTOPSIG(status)}") if os.WIFCONTINUED(status): print(" Continued")
pid = os.fork() if pid == 0: # Child process fate = random.choice([’exit’, ‘segfault’, ‘stop’]) if fate == ’exit’: os._exit(42) elif fate == ‘segfault’: os.kill(os.getpid(), signal.SIGSEGV) else: os.kill(os.getpid(), signal.SIGSTOP) os._exit(0) else: # Parent waits and analyzes child_pid, status = os.waitpid(pid, 0) analyze_status(child_pid, status)
The child randomly chooses to exit normally, segfault, or stop. The parent uses various macros to interpret the status returned by waitpid.
This demonstrates the full range of status interpretation possibilities when working with child processes in Python.
This example shows how os.waitpid handles orphaned processes that have been reparented to init (PID 1) when the original parent exits.
orphaned.py
import os import time import sys
pid = os.fork()
if pid == 0: # Child becomes orphan print(f"Child PID: {os.getpid()}") print(“Child sleeping for 10 seconds (will be orphaned)”) time.sleep(10) print(“Orphan child exiting”) sys.exit(0) else: # Parent exits immediately print(f"Parent exiting, child will be orphaned") sys.exit(0)
The parent exits immediately, orphaning the child which continues running. The original parent can no longer wait for this child as it’s been reparented.
This demonstrates an important limitation - you can only wait for direct child processes, not grandchildren or orphaned processes.
PID reuse: Be aware of potential PID reuse between wait calls
Zombie processes: Always wait for children to avoid zombies
Race conditions: Children may exit before waitpid is called
Permission issues: Need permissions to wait for specific processes
Signal interference: Signals can interrupt waitpid calls
Always wait: Prevent zombie processes by always waiting
Check status: Properly interpret the exit status
Handle errors: Account for ECHILD and EINTR errors
Use WNOHANG carefully: Avoid busy-waiting with WNOHANG
Consider subprocess: For new code, consider subprocess module
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.