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

    Server components representing isolated Python interpreters running in parallel
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    InterpreterPoolExecutor: True Parallelism in Python

    Learn Python InterpreterPoolExecutor for CPU-bound tasks, isolated interpreters, true parallelism, and safer concurrency design.

    Ler mais

    Tempo de leitura: 6 minutos
    13/09/2026
    Microprocessor representing CPUs available to a Python process
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    os.process_cpu_count: Count Available CPUs

    Learn Python os.process_cpu_count to size workers according to the CPUs actually available to the process.

    Ler mais

    Tempo de leitura: 4 minutos
    12/09/2026
    Laptop with digital code representing SQLite BLOB data
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    sqlite3.Blob: Incremental BLOB Reads and Writes

    Learn Python sqlite3.Blob for incremental BLOB reads and writes, lower memory use, and safer binary data handling in SQLite.

    Ler mais

    Tempo de leitura: 5 minutos
    12/09/2026
    Statistical analysis for Python random.binomialvariate
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    random.binomialvariate: Simulate Binomial Outcomes

    Learn Python random.binomialvariate to simulate successes, validate probabilities, and analyze binomial scenarios with practical examples.

    Ler mais

    Tempo de leitura: 5 minutos
    11/09/2026
    Python source code and function signature analysis
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    inspect.signature.bind: Validate Function Arguments

    Learn Python inspect.signature.bind to validate arguments, apply defaults, and build safer decorators and dynamic APIs.

    Ler mais

    Tempo de leitura: 4 minutos
    11/09/2026
    Python code for safe directory cleanup with shutil.rmtree
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    shutil.rmtree onexc: Handle Directory Removal Errors

    Learn shutil.rmtree with onexc in Python to remove directory trees, handle permissions, log failures, and build safer cleanup routines.

    Ler mais

    Tempo de leitura: 6 minutos
    10/09/2026