Python queue: Communicate Between Threads

Published on: August 28, 2026
Reading time: 5 minutes
Commuters line up at a subway station platform, showcasing public transportation dynamics.

The queue module provides synchronized queues for safe communication between threads. It implements FIFO, LIFO, priority, and simple queues with blocking operations, timeouts, maximum capacity, and task tracking. Producer-consumer designs become clearer because workers exchange messages instead of modifying shared containers directly.

A queue does not make processing automatically correct. The application still needs ownership rules, message schemas, backpressure, exception handling, shutdown, and idempotency. Also distinguish queue.Queue for threads from multiprocessing.Queue and asyncio.Queue.

Create a FIFO queue

Queue delivers items in insertion order.

from queue import Queue

queue = Queue()
queue.put("first")
queue.put("second")
print(queue.get())

The queue synchronizes its internal state across threads.

Set a capacity

maxsize limits the approximate number of pending items.

queue = Queue(maxsize=100)

A bounded queue creates backpressure when consumers cannot keep up.

Blocking put

By default, put() waits for space.

queue.put(item, timeout=5)

With a timeout, the operation raises queue.Full if insertion cannot complete.

put_nowait

put_nowait() attempts insertion immediately.

from queue import Full

try:
    queue.put_nowait(item)
except Full:
    record_drop(item)

Choose a policy: wait, reject, persist elsewhere, slow production, or report an error.

Blocking get

get() waits for an item.

item = queue.get(timeout=2)

When the deadline expires, it raises queue.Empty.

Do not rely on empty before get

empty(), full(), and qsize() are only snapshots in concurrent programs.

Another thread may change the queue before the next operation. Use nonblocking methods or timeouts and catch the exception.

Producer and consumer

from threading import Thread
from queue import Queue

queue = Queue(maxsize=20)


def consumer():
    while True:
        item = queue.get()
        try:
            process(item)
        finally:
            queue.task_done()

thread = Thread(target=consumer, daemon=True)
thread.start()

The finally block updates task tracking even after an error.

task_done

Every item removed by get() must receive exactly one task_done() call after its associated work is complete.

Calling it too many times raises ValueError; forgetting it can block join() forever.

join

queue.join() waits until every submitted task has been marked complete.

for item in items:
    queue.put(item)

queue.join()

This does not stop worker threads. It only waits on the unfinished-task counter.

Sentinels for shutdown

A unique object can tell a consumer to stop.

STOP = object()


def consumer():
    while True:
        item = queue.get()
        try:
            if item is STOP:
                return
            process(item)
        finally:
            queue.task_done()

Send one sentinel per independently consuming worker.

Shutdown order

Stop producers, allow normal input to finish, send sentinels, wait for queue.join(), and then join the threads.

A wrong order can leave work behind sentinels or block producers on a full queue.

Modern shutdown APIs

Recent Python versions provide explicit shutdown support for synchronized queues. These APIs prevent new insertion and wake blocked operations according to the selected policy.

Check the project’s minimum Python version and handle the queue-shutdown exception. Sentinels remain useful for broad compatibility.

LifoQueue

LifoQueue returns the newest item first.

from queue import LifoQueue

stack = LifoQueue()
stack.put("a")
stack.put("b")
print(stack.get())

It can favor recent work, but old items may starve.

PriorityQueue

PriorityQueue returns the smallest value first and uses heap semantics.

from queue import PriorityQueue

queue = PriorityQueue()
queue.put((10, "normal"))
queue.put((1, "urgent"))

Store priority, sequence, and payload to avoid comparing tasks directly.

Stable priority ties

from itertools import count

counter = count()
queue.put((priority, next(counter), task))

The sequence number preserves insertion order and prevents comparison of non-comparable payloads.

SimpleQueue

SimpleQueue is an unbounded FIFO with a smaller API.

from queue import SimpleQueue

queue = SimpleQueue()
queue.put(item)
item = queue.get()

Use it when you do not need capacity, task tracking, or built-in backpressure.

Choose the right queue

Use Queue for bounded FIFO and tracking, LifoQueue for synchronized stack behavior, PriorityQueue for priority ordering, and SimpleQueue for simple unbounded communication.

Document the choice because it affects fairness and memory.

Consumer exceptions

A failure should not silently kill every worker.

try:
    process(item)
except Exception as error:
    record_failure(item, error)
finally:
    queue.task_done()

Choose retry, dead-letter handling, system shutdown, or continuation.

Retries

Do not requeue the same item forever.

Track attempts, use backoff and deadlines, and move terminal failures aside. Side-effecting operations need idempotency.

Multiple consumers

More threads can improve I/O throughput but also increase pressure on databases, APIs, and filesystems.

Size worker counts according to downstream capacity.

The GIL and CPU work

Threads usually do not accelerate Python code that is continuously CPU-bound on traditional interpreter builds.

For CPU work, consider processes or native code that releases the GIL. See Python multiprocessing.

Mutable payloads

A queue transfers a reference, not a deep copy.

After put(), the producer should treat the object as owned by the consumer or send immutable data.

Message schemas

Use dataclasses, NamedTuple, or small objects with explicit fields such as type, payload, ID, attempt, and deadline.

Avoid long positional tuples and schema-free dictionaries.

Backpressure and locks

A bounded queue prevents unlimited growth, but blocked producers can still deadlock.

Never call blocking put() while holding a lock that the consumer needs.

Fairness

Thread wakeup order should not be treated as perfect fairness.

If fairness is a business requirement, model quotas, service classes, or separate queues explicitly.

Integration with selectors

Workers can send commands through a queue, while an I/O loop is awakened through a socket pair or pipe.

See Python selectors.

Integration with ThreadPoolExecutor

Executors already maintain an internal work queue. Avoid adding another layer without a reason.

A custom queue is useful when you need capacity, priority, explicit messages, or custom workers.

Timeouts and cancellation

A timeout on get() lets a worker check a stop flag.

while not stop.is_set():
    try:
        item = queue.get(timeout=0.5)
    except Empty:
        continue

Cancellation of work that already started must be cooperative.

Daemon threads

Daemon threads do not keep the process alive, but they may be interrupted without cleanup.

Use normal threads and explicit shutdown for important data.

Observability

Measure approximate queue size, waiting time, item age, arrival rate, throughput, failures, and retries.

qsize() is useful as an approximate metric, not a logical guarantee.

Security

Limit message count and size. Do not enqueue arbitrary callbacks supplied by users.

Redact secrets in logs and authorize work before producing tasks.

Testing

Test empty and full queues, timeouts, several producers and consumers, exceptions, sentinels, retries, shutdown, task_done(), and lock-related deadlocks.

Use synchronization events instead of exact sleeps.

Common mistakes

Common failures include using empty() as a guarantee, forgetting task_done(), confusing queue join with thread join, sending too few sentinels, allowing unlimited growth, blocking while holding a lock, and mutating payloads after put().

Conclusion

queue provides synchronized communication between threads. Use capacity for backpressure, task_done()/join() for tracking, and sentinels or explicit shutdown for termination.

Consult the official queue documentation, Python heapq, and Python selectors.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Data visualization and probability distribution for Python statistics.kde_random
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    statistics.kde_random: Sample from a KDE

    Learn Python statistics.kde_random to sample from a kernel density estimate with controlled bandwidth, reproducible seeds, validation, and practical testing.

    Ler mais

    Tempo de leitura: 5 minutos
    13/10/2026
    Linux workspace representing namespaces and Python os.setns automation
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    os.setns: Enter Linux Namespaces Safely

    Learn Python os.setns for entering Linux namespaces with safe permissions, validation, testing, and cleanup.

    Ler mais

    Tempo de leitura: 5 minutos
    13/10/2026
    Computer with code representing file copying with Python Path.copy
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Path.copy: Copy Files and Directories Safely

    Learn Python Path.copy to copy files and directories with explicit safety, consistency, performance, and compatibility.

    Ler mais

    Tempo de leitura: 5 minutos
    12/10/2026
    Code on a monitor representing native memory access with Python ctypes.memoryview_at
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    ctypes.memoryview_at: Zero-Copy Native Memory

    Learn Python ctypes.memoryview_at for zero-copy native memory access, pointer validation, ownership, and safer C integration.

    Ler mais

    Tempo de leitura: 6 minutos
    12/10/2026
    Python code on screen representing EnumDict and advanced enumerations
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    EnumDict: Build Custom Enums with Validation

    Learn Python EnumDict for custom EnumType metaclasses, member validation, and safer advanced enumerations.

    Ler mais

    Tempo de leitura: 4 minutos
    11/10/2026
    Server rack representing connection load balancing with Python SO_REUSEPORT_LB
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    SO_REUSEPORT_LB: Balance Connections Across Workers

    Learn Python SO_REUSEPORT_LB to distribute connections across multiple workers with safe testing, portability, and graceful shutdown.

    Ler mais

    Tempo de leitura: 5 minutos
    11/10/2026