Debugging hangs and crashes in multiprocessing, daemons or servers requires knowing what fork actually copies.
On Unix, new processes are created in two steps:
os.fork()duplicates the current process. It returns0in the child and the child's PID in the parent. The child gets a copy-on-write copy of memory and duplicates of all open descriptors.- An
os.exec*call (os.execv,os.execvp,os.execve...) replaces the current program image with another program. It never returns on success; the process keeps its PID and open inheritable descriptors.
The parent must reap children with os.waitpid(pid, 0), which returns (pid, status); decode with os.waitstatus_to_exitcode(status). Unreaped children linger as zombies. A child should finish with os._exit(code), not sys.exit, so it doesn't run the parent's cleanup handlers or flush its copied buffers twice.
The big trap: fork copies only the calling thread. If another thread held a lock (logging, malloc, an I/O buffer) at fork time, the child inherits it locked forever. Since 3.12, os.fork() emits a DeprecationWarning when the process has other threads, and in 3.14 multiprocessing on Linux defaults to the forkserver start method rather than fork. Use os.register_at_fork to reset state in children.
subprocess uses posix_spawn or fork+exec carefully for you; os.posix_spawn exposes the fast path directly. Windows has no fork.
Example
import os
import sys
if not hasattr(os, "fork"):
sys.exit("Unix only")
sys.stdout.flush() # don't let the child re-flush our buffer
pid = os.fork()
if pid == 0: # child
os.write(1, b"child running\n")
os._exit(7) # skip parent's cleanup handlers
_, status = os.waitpid(pid, 0) # parent reaps the child
print("exit code", os.waitstatus_to_exitcode(status)) # exit code 7
pid = os.fork()
if pid == 0:
os.execvp("echo", ["echo", "replaced by echo"]) # never returns
os.waitpid(pid, 0)
print("done") # doneRemember
forkreturns 0 in the child and the child's PID in the parentexec*replaces the program in the same process; alwayswaitpidyour children- Fork copies only the calling thread, so forking a multithreaded process is unsafe
Watch out for
- Calling
sys.exitin a forked child; useos._exitto avoid running the parent's atexit handlers and flushing buffers twice.