Skillversitas

Multiprocessing Basics

The multiprocessing module creates real OS processes, each with its own Python interpreter and memory. That gives true parallelism for CPU-bound work and crash isolation for risky code. The API mirrors threading: a Process class with target, start, and join, plus the __name__ == "__main__" guard you always need on Windows and macOS.

A Basic Process

import multiprocessing
import os

def worker(name, value):
    print(f"worker {name} in pid {os.getpid()} -> {value * 2}")

if __name__ == "__main__":
    procs = []
    for i in range(3):
        p = multiprocessing.Process(target=worker, args=(f"W{i}", i))
        p.start()
        procs.append(p)

    for p in procs:
        p.join()
    print("main pid:", os.getpid(), "- all workers done")

Each worker prints a different PID. The if __name__ == "__main__" guard is not optional style: on spawn platforms (Windows, macOS with the default start method) the child re-imports your module, and without the guard it would start the processes again, recursively.

Start Methods

ctx = multiprocessing.get_context("fork")    # Linux default, fastest
# "spawn" - new interpreter, child imports the module (Windows/macOS default)
# "forkserver" - spawn from a clean server process

fork copies the parent's memory (fast, but inherits locks and open files), spawn starts clean (slow, but predictable), and forkserver is a compromise. Whatever the method, functions and arguments passed to the worker must be picklable, which means they must be defined at module top level - closures and lambdas cannot cross the process boundary.

Daemon Processes

Process(daemon=True) marks the child for automatic termination when the parent exits. Use daemons for helper workers (heartbeats, log flushers) that should not outlive the application. Non-daemon children keep the parent's join() meaningful; a daemon that is killed mid-write can leave partial data, so make daemon work idempotent.

Exit Codes

p = multiprocessing.Process(target=crashy_function)
p.start()
p.join()
print(p.exitcode)   # 0 for clean exit, non-zero for an unhandled exception

A child that dies from an unhandled exception sets a non-zero exitcode but prints its traceback to the child's stderr - the parent must check the code if the failure matters.

Summary

Use multiprocessing.Process for CPU-bound parallelism, guard the entry point with __main__, keep target functions top-level and picklable, and check exitcode after join() to catch worker crashes.