Python Central HubSign in

Python Programming / Python MultiProcessing

Process (create, start, join)

Create processes using multiprocessing.Process, pass args, and wait for them using join().

Basic Process usage

Python
from multiprocessing import Process
 
 
def work(name: str) -> None:
    print("Working in process:", name)
 
 
if __name__ == "__main__":
    p = Process(target=work, args=("p1",))
    p.start()
    p.join()
    print("Main process finished")

Why join() matters

join() waits for the process to finish.

Without it:

  • your program might exit early
  • you might read results before they are ready

The four states of a Process object

A Process object exists before the operating system process does, and outlives it. Which attributes are readable depends on where you are in that lifecycle:

diagram Diagram mermaid

Measured on CPython 3.14.4, start method spawn, 8 logical CPUs:

stageis_alive()pidexitcode
after Process(...)FalseNoneNone
after start()True1952None
after join()False19520
child raisedFalseset1

Three things follow that catch people out:

  • pid is None until start(). There is no operating system process to have an id.
  • exitcode is None while the child runs. None means still running, not fine.
  • A child that raises prints its traceback and exits with 1. The parent carries on as if nothing happened — you only find out by checking exitcode.
Python
import multiprocessing as mp
 
def boom():
    raise ValueError("child failed")
 
if __name__ == "__main__":
    p = mp.Process(target=boom)
    p.start()
    p.join()
    print(p.exitcode)          # 1   <- the parent survived; nothing was raised here
    if p.exitcode != 0:
        raise RuntimeError("child failed, see traceback above")

See it move

Start the workers one way or the other and watch the clock. This is the single most common multiprocessing bug, and it is a bug of ordering, not of syntax.

sketch start-then-join, or start-and-join p5.js
Calling join() inside the same loop as start() waits for each child before launching the next, which runs them one at a time.

Four children, each sleeping 0.5 s, measured on this machine:

Python
ps = [mp.Process(target=slow, args=(0.5,)) for _ in range(4)]
for p in ps:
    p.start()
    p.join()            # 2.82 s  <- waits for each child before starting the next
 
ps = [mp.Process(target=slow, args=(0.5,)) for _ in range(4)]
for p in ps: p.start()  # launch everything first
for p in ps: p.join()   # 0.74 s  <- then collect

The first loop is not parallel at all. It is a sequential program that pays the cost of starting four processes for nothing.

The child does not inherit your variables

Under spawn — the default on Windows and macOS — the child starts a fresh interpreter and re-imports your module. It does not get a copy of the parent's current state; it gets whatever the module defines at import time.

Python
import multiprocessing as mp
 
counter = 0                      # module level: the child WILL see this
 
def bump():
    global counter
    counter += 100
    print("child sees", counter)  # child sees 100   <- started from 0, not 5
 
if __name__ == "__main__":
    counter = 5                   # inside the guard: the child never runs this
    p = mp.Process(target=bump)
    p.start(); p.join()
    print("parent", counter)      # parent 5   <- untouched

The child printed 100, not 105. It re-imported the module, saw counter = 0, and added 100 to its own copy. The parent's 5 was never visible to it, and the child's 100 was never visible back. Two separate memory spaces.

Check yourself

quizCheck yourself
  1. You call p = mp.Process(target=f). Before p.start(), what are p.pid and p.exitcode?

    Show answer

    B — both None, because no operating system process exists yet — The Process object is just a description until start() creates the real process. Measured: pid None and exitcode None before start, pid 1952 and exitcode None while running, exitcode 0 after join.

  2. A child process raises ValueError. What happens in the parent after p.join()?

    Show answer

    C — nothing is raised; the parent continues and p.exitcode is 1 — The child prints its traceback and exits with code 1. join() does not re-raise, so a parent that never checks exitcode treats a dead worker as a successful one.

  3. Why does 'for p in ps: p.start(); p.join()' take 2.82 s for four 0.5 s children, while starting all then joining all takes 0.74 s?

    Show answer

    B — calling join() inside the loop waits for each child to finish before the next is started, so nothing overlaps — join() blocks until that child exits. Putting it in the same loop as start() serializes the work while still paying process-creation cost — the worst of both approaches.

  4. counter = 0 at module level; inside the __main__ guard you set counter = 5; the child adds 100 and prints. Under spawn, what does the child print?

    Show answer

    B — 100 — Spawn starts a fresh interpreter that re-imports the module, so the child sees counter = 0 from module level. Code inside the __main__ guard never runs in the child, so the 5 is invisible to it.

🧪 Try It Yourself

Exercise 1 – Start a Process

exerciseYour turn

Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.

Output
Press Run to see what your code prints.

Exercise 2 – Process Pool map()

exerciseYour turn

Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.

Output
Press Run to see what your code prints.

Exercise 3 – Multiprocessing Queue

exerciseYour turn

Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.

Output
Press Run to see what your code prints.

Was this page helpful?

Let us know how we did