queue.SimpleQueue: Thread-Safe FIFO Queue

Published on: September 4, 2026
Reading time: 5 minutes
Software developer working with Python queues and threads

queue.SimpleQueue is a thread-safe FIFO queue for passing tasks or data between threads. It is designed for situations where you do not need a capacity limit, explicit task tracking, or manual lock management. Its interface is intentionally small: producers add objects with put(), and consumers remove them with get().

This makes SimpleQueue useful for worker pipelines, background processing, event collection, asynchronous logging, and communication between concurrent components inside one Python process.

What FIFO means

FIFO means first in, first out. The first object inserted is the first object returned.

from queue import SimpleQueue

queue = SimpleQueue()
queue.put("first")
queue.put("second")

print(queue.get())
print(queue.get())

The output is first and then second. Besides preserving order, the queue synchronizes access when multiple threads use it at the same time.

SimpleQueue versus Queue

queue.Queue includes a maximum capacity, task_done(), and join(). SimpleQueue is unbounded and does not track whether retrieved tasks have finished. Its smaller feature set is appropriate when all you need is a safe channel between threads.

Choose Queue when you need backpressure, bounded memory, or completion tracking. Choose SimpleQueue when you only need straightforward thread-safe transfer.

The producer-consumer pattern

A common design uses one thread to create work and another to process it.

from queue import SimpleQueue
from threading import Thread

queue = SimpleQueue()

def producer():
    for number in range(5):
        queue.put(number)
    queue.put(None)

def consumer():
    while True:
        item = queue.get()
        if item is None:
            break
        print(item * 2)

Thread(target=producer).start()
Thread(target=consumer).start()

Here, None acts as a sentinel indicating that no more items will arrive. In production code, a unique object is safer because None might be valid input.

STOP = object()

Blocking behavior

By default, get() blocks until an item is available. This avoids busy loops that repeatedly check the queue and waste CPU time.

You can use get_nowait() when the thread must continue doing other work. If no item exists, Python raises queue.Empty.

from queue import Empty, SimpleQueue

queue = SimpleQueue()
try:
    item = queue.get_nowait()
except Empty:
    print("no item is available")

Do not use empty as synchronization

empty() reports only a temporary observation. Another thread can add or remove an item immediately after the check. Therefore, it cannot guarantee that the next get() will not block.

When using non-blocking access, catch Empty. Concurrent state can change between any two instructions.

Threads, not processes

SimpleQueue is intended for threads inside the same process. It is not a replacement for multiprocessing.Queue when producers and consumers live in different processes.

For related control-flow concepts, see the guide to Python asyncio.Runner. Resource-heavy workers should also use predictable cleanup strategies, as discussed in Python weakref.finalize cleanup.

Event processing

A simple queue can separate event creation from slower processing.

from queue import SimpleQueue
from threading import Thread

EVENTS = SimpleQueue()
STOP = object()

def event_worker():
    while True:
        event = EVENTS.get()
        if event is STOP:
            return
        print(f"event: {event}")

worker = Thread(target=event_worker)
worker.start()
EVENTS.put({"type": "login", "user": 42})
EVENTS.put(STOP)
worker.join()

The producer returns quickly, while the consumer can persist, batch, validate, or transmit events.

Handling consumer exceptions

An unhandled exception can terminate a worker and leave queued objects unprocessed. Wrap task execution in a controlled exception boundary.

def safe_consumer():
    while True:
        item = queue.get()
        if item is STOP:
            break
        try:
            process(item)
        except Exception as error:
            record_failure(item, error)

Decide whether failed items should be discarded, retried, or sent to a separate error queue. Unlimited retries can create an endless loop, so production systems should include attempt limits.

Memory and backpressure

SimpleQueue has no maximum capacity. If producers consistently run faster than consumers, memory usage can continue growing. This is the most important operational limitation.

Use queue.Queue(maxsize=N) when input volume is unpredictable. The capacity limit causes producers to wait and creates backpressure. When work can be grouped, the article about Python itertools.batched explains how batching can reduce repeated calls.

Multiple consumers

Several threads can call get() on one queue. Each item is removed by one consumer. The exact distribution depends on scheduling, so do not assume round-robin behavior or task affinity.

workers = [Thread(target=safe_consumer) for _ in range(4)]
for worker in workers:
    worker.start()

When shutting down, add one sentinel for every worker. A single sentinel stops only the thread that receives it.

Mutable objects

The queue transfers object references. It does not copy objects. If a producer changes a mutable object after inserting it, the consumer may observe unexpected state. Prefer immutable values, snapshots, dataclasses treated as immutable, or copies created before calling put().

When not to use SimpleQueue

Do not use it for communication between machines, durable delivery, persistence across process restarts, or recovery after a crash. Systems that require those guarantees should use a message broker or durable stream such as RabbitMQ, Redis Streams, Kafka, or a managed queue.

It is also not a drop-in replacement for asyncio.Queue. Calling blocking get() from an event-loop thread can freeze asynchronous execution.

Testing queue-based workers

Test FIFO order, sentinel shutdown, concurrent production, exception handling, and clean termination. Avoid tests based only on arbitrary sleep() calls because they can be slow and unreliable. Prefer events, joins, and controlled timeouts.

The guide to Python StrEnum provides a useful way to represent closed worker states such as waiting, running, failed, and stopped.

Operational monitoring

Because the queue is unbounded, monitor production rate, consumption rate, processing latency, worker failures, and memory use. qsize() can be useful as an approximate metric, but it should not drive correctness decisions because concurrent changes can occur immediately.

Best practices

Use a unique sentinel, document who owns shutdown, catch task exceptions, stop threads with join(), keep queued values small, avoid modifying objects after insertion, and choose bounded Queue when memory pressure must be controlled. Keep the worker protocol explicit so maintainers know whether retries and error queues exist.

References

Read the official queue.SimpleQueue documentation and the official threading documentation.

Conclusion

queue.SimpleQueue is a focused solution for FIFO communication between threads. It handles synchronization and offers a small, readable interface. It is most effective when no capacity limit or completion counter is required. Its unbounded nature remains the key design consideration: measure the balance between producers and consumers, and switch to Queue(maxsize) whenever backpressure is necessary.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Software developer working with Python enums and API code
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python StrEnum: String Enums

    Learn Python StrEnum for string-based enums, input validation, JSON serialization, APIs, configuration, and cleaner domain contracts.

    Ler mais

    Tempo de leitura: 4 minutos
    04/09/2026
    Folders and directories for Python contextlib.chdir
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    contextlib.chdir: Restore Directories Automatically

    Learn Python contextlib.chdir to change directories temporarily, restore paths safely, isolate tests, and avoid global-state bugs.

    Ler mais

    Tempo de leitura: 5 minutos
    03/09/2026
    Python code execution and performance monitoring
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    sys.monitoring: Low-Overhead Instrumentation

    Learn Python sys.monitoring for low-overhead instrumentation with selective events, callbacks, tooling, and safe observability.

    Ler mais

    Tempo de leitura: 5 minutos
    03/09/2026
    Software developer organizing object data with Python operator.attrgetter
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    operator.attrgetter: Sort Objects by Attributes

    Learn Python operator.attrgetter to sort, group, and transform objects by simple or nested attributes with clearer reusable code.

    Ler mais

    Tempo de leitura: 4 minutos
    02/09/2026
    Asynchronous programming with Python asyncio.Runner
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    asyncio.Runner: Reuse the Event Loop Safely

    Learn Python asyncio.Runner to reuse an event loop, control context, signals, debug mode, cancellation, and safe asynchronous shutdown.

    Ler mais

    Tempo de leitura: 6 minutos
    02/09/2026
    Binary data compression with Zstandard in Python
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    compression.zstd: Zstandard Streams and Dictionaries

    Learn Python compression.zstd for Zstandard compression, streaming, dictionaries, safe limits, testing, and production workflows.

    Ler mais

    Tempo de leitura: 6 minutos
    01/09/2026