Python Central HubSign in

Python Programming / Python MultiProcessing

Sharing Data (Queue, Pipe, Manager)

Learn safe inter-process communication with Queue/Pipe and shared state with Manager.

Why sharing is different

Processes do not share memory like threads.

So you can’t safely update a global variable and expect other processes to see it.

Use a Queue to send messages/results.

Python
from multiprocessing import Process, Queue
 
 
def worker(q: Queue, x: int) -> None:
    q.put(x * x)
 
 
if __name__ == "__main__":
    q = Queue()
 
    procs = [Process(target=worker, args=(q, i)) for i in range(5)]
    for p in procs:
        p.start()
 
    results = [q.get() for _ in procs]
 
    for p in procs:
        p.join()
 
    print(sorted(results))

Pipe

Pipe is a two-way connection.

Python
from multiprocessing import Process, Pipe
 
 
def worker(conn):
    conn.send("hello")
    conn.close()
 
 
if __name__ == "__main__":
    parent, child = Pipe()
    p = Process(target=worker, args=(child,))
    p.start()
    print(parent.recv())
    p.join()

Manager (shared dict/list)

Manager provides proxy objects.

Python
from multiprocessing import Process, Manager
 
 
def worker(shared, i):
    shared[i] = i * i
 
 
if __name__ == "__main__":
    with Manager() as manager:
        shared = manager.dict()
 
        procs = [Process(target=worker, args=(shared, i)) for i in range(5)]
        for p in procs:
            p.start()
        for p in procs:
            p.join()
 
        print(dict(shared))

Guidance

  • Prefer passing data via Queue when possible.
  • Use Manager for simple shared state (slower than local memory).

Choosing a channel

Processes do not share memory, so everything they exchange must be copied across a boundary. The four tools differ in shape, not in power:

diagram Diagram mermaid

Start with the mistake this all exists to prevent:

Python
import multiprocessing as mp
 
def add(lst):
    lst.append("from child")
 
if __name__ == "__main__":
    lst = []
    p = mp.Process(target=add, args=(lst,)); p.start(); p.join()
    print(lst)          # []   <- the child appended to ITS OWN copy

The list was pickled into the child. The child's append succeeded — in the child. The parent's list is untouched, and no error is raised anywhere.

The four channels, measured

Python
q = mp.Queue()                     # FIFO, any number of readers and writers
# child: q.put(i*i) for i in range(5); q.put(None)
# parent receives: [0, 1, 4, 9, 16]      None used as the "done" sentinel
 
parent_conn, child_conn = mp.Pipe()   # exactly two ends
# child: conn.send({"pid": ..., "payload": [1, 2, 3]})
# parent: conn.recv() -> {'pid': 19392, 'payload': [1, 2, 3]}
 
with mp.Manager() as m:            # a real server process holding the objects
    d, l = m.dict(), m.list([1, 2])
    # after the child writes: d -> {'child': 'wrote this'}   l -> [1, 2, 99]

A Pipe carries arbitrary picklable objects, not bytes — the dict above arrived intact. A Manager dict is a proxy: every read and write is a round trip to a separate server process, which is why it is the most convenient and the slowest.

See it move

Value is genuine shared memory, and that is exactly why it can be corrupted. v.value += 1 is three operations — read, add, write — and a second process can land between them. Run it with the lock off and watch updates disappear.

sketch Lost updates without a Lock p5.js
v.value += 1 is read, add, write. Two processes interleaving those steps overwrite each other, so increments are silently lost.

Two processes, 10,000 increments each, measured:

resultexpectedlost
with mp.Lock()20,00020,0000
without a lock15,34720,0004,653

Nearly a quarter of the work vanished, with no exception and no warning. The number differs on every run, which is the signature of a race — and the reason a test that passes once proves nothing here.

Python
v = mp.Value("i", 0)
lock = mp.Lock()
 
def worker(v, lock):
    for _ in range(10_000):
        with lock:          # remove this line and increments are lost
            v.value += 1

What the copying costs

Python
# 200,000 ints put on a Queue and taken off again: 33 ms

Every object crossing a process boundary is pickled on one side and unpickled on the other. That is why sending a large DataFrame to a worker can cost more than the work the worker was sent to do — and why it is usually better to send a description of the task, like a range of indices, and let the child load the data itself.

Check yourself

quizCheck yourself
  1. A parent passes an empty list to a child, which appends to it. What does the parent see afterwards?

    Show answer

    B — an empty list, because the child received a pickled copy — Arguments are pickled into the child. The child mutated its own copy successfully, so nothing raises — the parent's list is simply still empty.

  2. Two processes each run v.value += 1 ten thousand times on a shared Value with no Lock. What is a realistic result?

    Show answer

    C — a varying number below 20000, such as 15347 — += is read, add, write. Interleaving those steps loses updates. Measured 15347 of 20000 on one run, and a different figure on the next — that variability is what identifies a race.

  3. Why does a consumer loop reading mp.Queue conventionally need a sentinel such as None?

    Show answer

    B — because q.get() on an empty queue blocks forever, so the consumer needs an explicit end signal — An empty queue is indistinguishable from one whose producer is merely slow, so get() waits. Without a sentinel the program hangs using no CPU and raising nothing.

  4. Which channel is a proxy backed by a separate server process, making it the most convenient and the slowest?

    Show answer

    C — Manager — A Manager hosts the real dict or list in its own process; every read and write is a round trip. Value and Array are true shared memory, and Pipe is a direct two-ended link.

🧪 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