The essentials
Quick reference
One focused task per row. Jump to the related section for complete, working examples.
| Use | Syntax | Examples |
|---|---|---|
| List available start methods | methods = multiprocessing.get_all_start_methods() | View examples |
| Create an explicit context | ctx = multiprocessing.get_context('spawn') | View examples |
| Set the global start method | multiprocessing.set_start_method('spawn') | View examples |
| Guard application startup | if __name__ == '__main__': main() | View examples |
| Start one process | process = ctx.Process(target=worker, args=(input_value,)); process.start() | View examples |
| Wait for process completion | process.join(timeout=5) | View examples |
| Release a Process handle | process.close() | View examples |
| Map work through a pool | with ctx.Pool(processes=4) as pool: results = pool.map(transform, items, chunksize=32) | View examples |
| Submit an asynchronous pool call | pending = pool.apply_async(transform, (item,)); result = pending.get(timeout=5) | View examples |
| Drain and close a pool | pool.close(); pool.join() | View examples |
| Send a queued message | queue.put(message); received = queue.get(timeout=5) | View examples |
| Create a one-way pipe | receiver, sender = ctx.Pipe(duplex=False) | View examples |
| Wait for ready connections | ready = multiprocessing.connection.wait(connections, timeout=5) | View examples |
| Signal workers with an Event | ready.set(); ready.wait(timeout=5) | View examples |
| Protect a critical section | with lock: shared_value.value += 1 | View examples |
| Create a synchronized scalar | counter = ctx.Value('i', 0) | View examples |
| Create a shared-memory block | block = SharedMemory(create=True, size=byte_count) | View examples |
| Attach by shared-memory name | block = SharedMemory(name=block_name) | View examples |
| Close and unlink shared memory | block.close(); block.unlink() | View examples |
| Opt out of resource tracking | block = SharedMemory(name=block_name, track=False) | View examples |
| Run a manager server | with ctx.Manager() as manager: shared = manager.dict() | View examples |
| Create a managed list | shared_items = manager.list(initial_items) | View examples |
| Receive only trusted objects | message = connection.recv() | View examples |
| Use an importable worker | def worker(payload): return process_payload(payload) | View examples |
| Request cooperative shutdown | stop_event.set(); process.join(timeout=5) | View examples |
| Terminate an unresponsive process | if process.is_alive(): process.terminate(); process.join() | View examples |
multiprocessing runs Python work in separate processes, enabling CPU parallelism on ordinary CPython builds at the cost of process startup, serialization, and explicit resource ownership. Keep worker functions importable, protect application startup with a main guard, choose a start context deliberately when consistency matters, and prefer messages over shared mutable state. When copying large buffers dominates the workload, shared memory can help—but only with strict synchronization and a single, documented cleanup owner.
Step by step
Detailed examples
Choose a start context as part of the application contract
The start method controls what a child inherits and how it imports application code. In Python 3.14, forkserver became the POSIX default where supported; spawn remains the default on macOS and Windows, and fork is no longer the default on any platform. Spawn starts a fresh interpreter, while forkserver asks a usually single-threaded server to fork children; both require picklable arguments and an importable main module. Fork can be fast but is unsafe around many multithreaded runtimes, and os.fork() may emit a DeprecationWarning when Python detects multiple threads. Libraries should accept a caller-provided context instead of globally selecting one. Applications that need consistency may use get_context(), while set_start_method() belongs inside the main guard and normally runs only once.
import multiprocessing as mp
def main() -> None:
context = mp.get_context('spawn')
print(context.get_start_method())
print('spawn' in mp.get_all_start_methods())
if __name__ == '__main__':
main() spawn
TrueGuard startup and own every Process lifecycle
With spawn and forkserver, the child imports the main module, so top-level process creation can recurse indefinitely. Put startup in a function, call it under if __name__ == '__main__', and keep targets at module scope. start() launches once; join() waits but does not stop a child, and a timeout merely returns. After joining, check exitcode rather than assuming success. A normal return is zero, an uncaught exception is usually one, and POSIX signal exits are negative. close() releases the parent-side Process resources only after the child has stopped. Explicit ownership avoids zombie processes on POSIX and leaked handles elsewhere.
import multiprocessing as mp
def double(value: int, output: mp.Queue) -> None:
output.put(value * 2)
def main() -> None:
context = mp.get_context('spawn')
output = context.Queue()
process = context.Process(target=double, args=(21, output))
process.start()
print(output.get(timeout=5))
process.join(timeout=5)
print(process.exitcode)
output.close()
process.close()
if __name__ == '__main__':
main() 42
0Reuse a bounded Pool for independent CPU tasks
A Pool amortizes worker startup across many independent calls. map() preserves input order; imap_unordered() can reduce head-of-line blocking when order is irrelevant. Tune chunksize against real item cost because larger chunks reduce IPC overhead but can hurt load balancing. AsyncResult.get() transports worker exceptions back to the parent and can bound the wait. Consume every result so failures are visible. close() followed by join() is graceful; terminate() abandons outstanding work. A Pool context manager calls terminate() on exit, so ensure required results are consumed inside the block and use an explicit close/join lifecycle when accepted tasks must drain during surrounding error handling.
import multiprocessing as mp
def square(value: int) -> int:
return value * value
def main() -> None:
context = mp.get_context('spawn')
with context.Pool(processes=2) as pool:
results = pool.map(square, [4, 2, 3], chunksize=1)
print(results)
if __name__ == '__main__':
main() [16, 4, 9]Prefer explicit messages and drain them before joining producers
Queue supports multiple producers and consumers and serializes each object through a pipe using a feeder thread. A producer that has queued buffered data may not exit until that data is flushed, so receive expected messages before joining it; joining first can deadlock when the pipe fills. close() and join_thread() let the owning process finish its feeder deliberately. Pipe() is lighter for point-to-point traffic: with duplex=False, the first endpoint receives and the second sends. Do not let multiple threads or processes concurrently use the same pipe endpoint because messages may be corrupted. Both Queue and Connection receive operations unpickle objects, so their inputs must be trusted.
import multiprocessing as mp
def produce(sender) -> None:
sender.send({'status': 'ready', 'count': 3})
sender.close()
def main() -> None:
context = mp.get_context('spawn')
receiver, sender = context.Pipe(duplex=False)
process = context.Process(target=produce, args=(sender,))
process.start()
sender.close()
message = receiver.recv()
receiver.close()
process.join(timeout=5)
print(message['status'])
print(message['count'])
if __name__ == '__main__':
main() ready
3Share the smallest state and synchronize the whole invariant
Event communicates a state change without busy-waiting; wait(timeout) lets a worker retain a shutdown or recovery path. Lock protects a critical section across processes and should be used as a context manager. Value and Array place ctypes values in shared memory and are synchronized by default, but an expression such as value.value += 1 is a read-modify-write sequence and is not automatically atomic as a whole. Hold the object's lock, or a separate lock that covers the complete application invariant. Never hold a lock while performing unbounded I/O or waiting for another worker.
import multiprocessing as mp
def increment(ready, counter) -> None:
if not ready.wait(timeout=5):
return
with counter.get_lock():
counter.value += 1
def main() -> None:
context = mp.get_context('spawn')
ready = context.Event()
counter = context.Value('i', 0)
workers = [context.Process(target=increment, args=(ready, counter)) for _ in range(3)]
for worker in workers:
worker.start()
ready.set()
for worker in workers:
worker.join(timeout=5)
print(counter.value)
if __name__ == '__main__':
main() 3Use manager proxies for flexibility, not high-throughput updates
Manager() starts a server process that owns ordinary Python objects and exposes proxies to other processes. This supports flexible dictionaries, lists, sets in Python 3.14, namespaces, queues, and synchronization primitives—even across machines with a configured BaseManager—but every proxy operation is IPC and may serialize data. Batch work instead of issuing tiny remote operations in a loop. Nested ordinary mutable values are not automatically observed when edited in place; reassign the changed value or store a managed proxy inside the outer proxy. Protect a proxy shared by multiple threads, and close the manager context to shut down its server.
import multiprocessing as mp
def record(shared, key: str, value: int) -> None:
shared[key] = value * value
def main() -> None:
context = mp.get_context('spawn')
with context.Manager() as manager:
shared = manager.dict()
workers = [context.Process(target=record, args=(shared, key, value)) for key, value in [('b', 3), ('a', 2)]]
for worker in workers:
worker.start()
for worker in workers:
worker.join(timeout=5)
print(sorted(shared.items()))
if __name__ == '__main__':
main() [('a', 4), ('b', 9)]Treat pickling as compatibility and trust boundaries
Spawn, forkserver, queues, pipes, pools, and managers serialize many callables and values with pickle. Define workers and custom classes at importable module scope; lambdas, nested functions, open files, locks from an incompatible context, and live network clients are unsuitable payloads. Send compact identifiers and immutable data rather than large object graphs or parent runtime state. Unpickling can execute attacker-chosen code, so never receive multiprocessing messages from an untrusted peer. Listener and Client can authenticate peers with HMAC, but authentication does not make pickle safe for an attacker holding the key and does not encrypt traffic.
import pickle
from dataclasses import dataclass
@dataclass(frozen=True)
class Job:
item_id: int
operation: str
job = Job(item_id=17, operation='normalize')
payload = pickle.dumps(job, protocol=pickle.HIGHEST_PROTOCOL)
restored = pickle.loads(payload)
print(restored == job)
print(restored.operation) True
normalizeDesign cooperative shutdown before forceful termination
A timeout reports that a process is still running; it does not cancel work. Prefer an Event, sentinel message, or finite input channel so the worker can release locks, close shared-memory handles, and flush results. terminate() skips finally blocks and exit handlers, can corrupt queues or pipes, and can leave acquired locks permanently blocking peers; kill() is even less graceful. Python 3.14 adds Process.interrupt() on POSIX, but its Windows behavior is undefined and workers may catch KeyboardInterrupt. If force is unavoidable, isolate that process from shared synchronization, terminate only after a grace period, join it to reap resources, and recreate any affected IPC. Drain Queue output before joining producers to avoid feeder-thread deadlocks.
import multiprocessing as mp
def wait_for_stop(stop) -> None:
while not stop.wait(timeout=0.05):
pass
def main() -> None:
context = mp.get_context('spawn')
stop = context.Event()
process = context.Process(target=wait_for_stop, args=(stop,))
process.start()
stop.set()
process.join(timeout=5)
if process.is_alive():
process.terminate()
process.join()
print(process.exitcode)
if __name__ == '__main__':
main() 0Local code tester
Plan deterministic process-pool chunks
Partition independent items into bounded chunks before submitting them to worker processes; this planning step runs without starting processes.
Press Run to load Python locally.
Sources and further reading
References
Authoritative documentation used to verify and expand this cheat sheet.
- Python Software Foundationmultiprocessing — Process-based parallelismdocs.python.org
- Python Software FoundationContexts and start methodsdocs.python.org
- Python Software Foundationmultiprocessing.shared_memory — Direct shared-memory accessdocs.python.org
- Python Software FoundationProgramming guidelines for multiprocessingdocs.python.org
- Python Software Foundationpickle — Python object serializationdocs.python.org
- Python Software FoundationPEP 371 — Addition of multiprocessing to the standard librarypeps.python.org
Help us improve
Found a typo or missing example?
Tell us what would make this cheat sheet clearer, more complete, or more useful.



