os.fork
One call, two processes: after fork() the parent and an exact copy of it both continue from the same line, told apart only by the return value. POSIX only - fork, forkpty and register_at_fork do not exist on Windows (nor on WASI, Android or iOS).
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| before | callable | no (None) | register_at_fork(): called in the parent just before forking. Before-hooks run in reverse registration order. |
| after_in_parent | callable | no (None) | register_at_fork(): called in the parent after the fork, in registration order. |
| after_in_child | callable | no (None) | register_at_fork(): called in the child after the fork, in registration order - the place to reset locks, random seeds or connections. |
Return value
int | tuple[int, int] | None — fork(): 0 in the child, the child pid in the parent. forkpty(): (pid, fd) where fd is the master end of the pseudo-terminal. register_at_fork(): None.
Common patterns
import os pid = os.fork() if pid == 0: try: print('child working', flush=True) finally: os._exit(0) else: _, status = os.waitpid(pid, 0) print('child exit code', os.waitstatus_to_exitcode(status))
import os, random os.register_at_fork(after_in_child=random.seed)
import os pid, fd = os.forkpty() if pid == 0: os.execlp('ls', 'ls', '--color=auto') else: output = os.read(fd, 1024) os.waitpid(pid, 0)
import multiprocessing as mp def work(n): return n * n if __name__ == '__main__': with mp.get_context('spawn').Pool(2) as pool: print(pool.map(work, range(4)))
Examples
Pitfalls
import subprocess, sys code = 'import sys\ntry:\n sys.exit(0)\nexcept SystemExit:\n print("child kept running")' subprocess.run([sys.executable, '-c', code], capture_output=True, text=True).stdout
import subprocess, sys code = 'import os\ntry:\n os._exit(0)\nexcept SystemExit:\n print("child kept running")' subprocess.run([sys.executable, '-c', code], capture_output=True, text=True).stdout
import subprocess, sys code = 'import os; print("result", end=""); os._exit(0)' subprocess.run([sys.executable, '-c', code], capture_output=True, text=True).stdout
import subprocess, sys code = 'import os; print("result", end="", flush=True); os._exit(0)' subprocess.run([sys.executable, '-c', code], capture_output=True, text=True).stdout
When to use
- Unix daemons and servers that pre-fork workers
- fork followed directly by an exec* call (what subprocess does under the hood on many systems)
- register_at_fork: libraries that must reset locks, seeds or sockets in forked children
- Code that must run on Windows → subprocess or multiprocessing
- Processes that already run threads → fork copies only the calling thread (3.12+ warns, see notes)
- macOS programs using system frameworks (including urllib.request) - the docs call fork unsafe there
Notes
FAQ
No. fork is POSIX only, and forkpty and register_at_fork are Unix only (none exist on Windows, WASI, Android or iOS), so on Windows os.fork raises AttributeError. Examples here never fork the page process; they show the portable alternatives - subprocess and multiprocessing with the spawn start method - and the child-process rules in a separate Python child.