Python Central HubSign in

Python Programming / Python Threading

Thread Communication (Queue)

Use queue.Queue for safe producer-consumer patterns without manually managing locks.

diagram a queue is how threads hand work over without sharing state mermaid
Two threads touching the same list need a lock and a way to wait. queue.Queue is that lock plus that waiting, already correct: get blocks until an item exists, put blocks when a bounded queue is full, and task_done with join gives you a way to know the work is finished.

Why Queue?

queue.Queue is thread-safe.

Use it to:

  • pass tasks from producer threads to worker threads
  • avoid manual lock management

Producer-consumer example

Python
import threading
import queue
import time
 
q = queue.Queue()
 
 
def producer():
    for i in range(5):
        q.put(i)
        print("produced", i)
    q.put(None)  # sentinel
 
 
def consumer():
    while True:
        item = q.get()
        try:
            if item is None:
                break
            print("consuming", item)
            time.sleep(0.2)
        finally:
            q.task_done()
 
 
t1 = threading.Thread(target=producer)
t2 = threading.Thread(target=consumer)
 
t1.start(); t2.start()
 
t1.join()
q.join()  # wait until all tasks are marked done
print("All tasks processed")

Tips

  • Use a sentinel (None) to stop consumers.
  • Always call task_done() (often in a finally).
sketch A queue is a lock plus a place to wait, already written p5.js
Two threads sharing a list would need a lock and a way to sleep until there is work. queue.Queue is both. The consumer parks when it is empty rather than spinning, and a bounded queue parks the producer when it is full -- which is what stops a fast producer from filling memory.

Why threads need a safe channel

Threads share memory, which makes it easy to pass data around and equally easy to corrupt it. Sharing a plain list means guarding every read and write with a lock. queue.Queue does that work for you: put and get are already thread-safe, and get waits politely when the queue is empty, so a consumer never spins in a busy loop.

Following the example

The producer puts five numbers on the queue and then a None sentinel. The consumer loops on q.get(), which blocks until an item is available. For each real item it prints, does some work, and calls task_done() in a finally block. When it receives None it breaks out. In the main thread, t1.join() waits for the producer, and q.join() waits until every item, including the sentinel, has been marked done. The output lines from producer and consumer may interleave differently on each run, which is normal.

Several workers sharing a job list

A queue also spreads work across several consumers. Each item goes to exactly one consumer:

Python
import queue
import threading
 
jobs: queue.Queue = queue.Queue()
results: queue.Queue = queue.Queue()
 
 
def worker() -> None:
    while True:
        n = jobs.get()
        if n is None:
            jobs.task_done()
            return
        results.put(n * n)
        jobs.task_done()
 
 
threads = [threading.Thread(target=worker) for _ in range(3)]
for t in threads:
    t.start()
 
for n in range(10):
    jobs.put(n)
for _ in threads:
    jobs.put(None)  # one stop signal per worker
 
jobs.join()
for t in threads:
    t.join()
 
print(sorted(results.get() for _ in range(results.qsize())))

The output is [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]. Sorting makes it independent of which worker finished first.

Useful variations

  • Queue(maxsize=10) makes put wait when full, which applies backpressure to fast producers.
  • q.get(timeout=1) raises queue.Empty instead of waiting forever.
  • q.get_nowait() and q.put_nowait() never block.
  • queue.LifoQueue and queue.PriorityQueue change the order items come out.

Common mistakes

  • Forgetting task_done(), so join() never returns.
  • Too few sentinels for the number of consumers.
  • Using a plain list shared between threads without a lock.
  • Using qsize() or empty() to make decisions. Another thread may change the queue right after you check.

When to use it

Use a queue whenever one thread produces work and others consume it: downloaders feeding a parser, a logger thread collecting messages, or a pool of workers processing jobs.

Check yourself

quizCheck yourself
  1. A consumer calls `q.get()` on an empty `queue.Queue`. What happens?

    Show answer

    C — It blocks until an item arrives — It parks, using no CPU, until something is put. `get_nowait()` is the version that raises `Empty` instead.

  2. What does `maxsize` give you beyond a memory limit?

    Show answer

    B — Back-pressure — a full queue blocks the producer — With `maxsize=0` (unbounded) a producer faster than its consumer will grow the queue until memory runs out. A bound makes the producer wait instead.

  3. What is the point of `task_done()` and `join()`?

    Show answer

    B — Knowing when every item that was put has been fully handled — `join()` returns when every `put` has a matching `task_done`. Note it tracks completion of the WORK, not merely that the queue is empty.

  4. How do you tell worker threads to stop?

    Show answer

    B — Put one sentinel value per worker on the queue — Each worker takes one sentinel and exits, so every worker gets exactly one. A queue has no close, and killing threads is not possible.

🧪 Try It Yourself

Exercise 1 – Put and Get

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 – Producer-Consumer

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 – Queue Size

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