Producer-consumer with queue.Queue

threading & multiprocessing · Practical · 11 min

Queues replace most hand-written locking and give natural back-pressure between pipeline stages.

queue.Queue is a thread-safe FIFO: all locking is inside. It's the standard way for threads to communicate.

  • q.put(item) adds; with maxsize set, it blocks while the queue is full, which slows producers to the consumers' pace (back-pressure).
  • q.get() blocks until an item is available. timeout= raises queue.Empty / queue.Full.
  • After processing each item, call q.task_done(); q.join() waits until every item put has been marked done.

Stopping consumers is the tricky part. Classic approaches put one sentinel (such as None) per worker. Python 3.13 adds `q.shutdown()`: further put() raises queue.ShutDown, and get() raises ShutDown once the queue is drained, so workers exit cleanly. shutdown(immediate=True) also discards remaining items.

Variants: LifoQueue (stack), PriorityQueue (smallest item first; use (priority, counter, item) tuples), and SimpleQueue (unbounded, no task_done, reentrant-safe).

These queues are for threads. Between processes use multiprocessing.Queue; between asyncio tasks use asyncio.Queue.

Example

import queue, threading

q: queue.Queue[int] = queue.Queue(maxsize=5)
results, lock = [], threading.Lock()

def worker():
    while True:
        try:
            n = q.get()
        except queue.ShutDown:        # 3.13+: queue shut down and empty
            return
        with lock:
            results.append(n * n)
        q.task_done()

workers = [threading.Thread(target=worker) for _ in range(3)]
for w in workers: w.start()
for n in range(10):
    q.put(n)                          # blocks while 5 items are waiting
q.join()                              # all items processed
q.shutdown()                          # wake workers so they exit
for w in workers: w.join()
print(sorted(results))                # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Remember

  • queue.Queue is thread-safe; put/get block with optional timeouts
  • task_done() + join() track completion
  • Queue.shutdown() (3.13) stops consumers without sentinels

Watch out for

  • Forgetting task_done(), so q.join() never returns.

Check yourself

  1. A Queue has maxsize=2 and holds 2 items. What does put() do?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/library/queue.html