RabbitMQ
Pika
Synchronous Consumption
Blocking Consumption
Message Queuing

Synchronous and blocking consumption in RabbitMQ using pika

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

In Pika, the easiest way to consume RabbitMQ messages is often the blocking API. That style is a good fit for dedicated worker processes, but it helps to distinguish two related ideas: a blocking connection model and a synchronous application flow that handles one message at a time.

BlockingConnection Is the Usual Starting Point

Pika provides BlockingConnection for straightforward consumer code. Once you use it, the two common consumption patterns are basic_get and basic_consume with start_consuming().

The first pattern looks more explicitly synchronous because your code asks for one message and continues after that single call.

python
1import pika
2
3connection = pika.BlockingConnection(pika.ConnectionParameters("localhost"))
4channel = connection.channel()
5channel.queue_declare(queue="tasks", durable=True)
6
7method, properties, body = channel.basic_get(queue="tasks", auto_ack=False)
8
9if method is None:
10    print("No message available")
11else:
12    print("Received:", body.decode())
13    channel.basic_ack(method.delivery_tag)
14
15connection.close()

This is easy to reason about, but it is not the most efficient pattern for long-running workers because it polls instead of waiting for pushed deliveries.

Use basic_consume for Normal Workers

For a real worker process, basic_consume with start_consuming() is usually the better blocking pattern.

python
1import time
2import pika
3
4connection = pika.BlockingConnection(pika.ConnectionParameters("localhost"))
5channel = connection.channel()
6channel.queue_declare(queue="tasks", durable=True)
7
8
9def handle_message(ch, method, properties, body):
10    print("Received:", body.decode())
11    time.sleep(1)
12    ch.basic_ack(delivery_tag=method.delivery_tag)
13
14
15channel.basic_qos(prefetch_count=1)
16channel.basic_consume(queue="tasks", on_message_callback=handle_message)
17
18print("Waiting for messages")
19channel.start_consuming()

start_consuming() blocks the current thread and runs Pika's event loop. From the application's perspective, that process is now dedicated to receiving messages and handling them serially through the callback.

Know When to Use Each Pattern

Use basic_get when you really want pull-style behavior, such as a small maintenance script that checks for work occasionally.

Use basic_consume for a worker that should sit on the queue continuously. RabbitMQ is optimized for active consumers receiving pushed messages, so this is the common production pattern.

A small producer makes local testing easy.

python
1import pika
2
3connection = pika.BlockingConnection(pika.ConnectionParameters("localhost"))
4channel = connection.channel()
5channel.queue_declare(queue="tasks", durable=True)
6
7channel.basic_publish(
8    exchange="",
9    routing_key="tasks",
10    body="process report",
11    properties=pika.BasicProperties(delivery_mode=2),
12)
13
14print("Sent message")
15connection.close()

Run the producer, then run the consumer. The consumer blocks until delivery, processes the message, acknowledges it, and waits again.

Manual Acknowledgement Matters

Blocking consumption is simple, but message acknowledgement still matters. auto_ack=True may look convenient, but it marks the message as handled before your code actually finishes the work.

For real jobs, manual acknowledgement is usually safer because a crash during processing should leave the message eligible for redelivery.

basic_qos(prefetch_count=1) is also important for fairness. It prevents one blocking worker from being flooded with many unacknowledged messages while others sit idle.

When Blocking Consumption Is a Good Fit

Blocking Pika consumers are a good choice when:

  • one process is dedicated to queue work
  • the code path is simple and sequential
  • throughput is moderate
  • easy debugging matters more than async integration

If the same process must also serve HTTP traffic, manage many concurrent sockets, or integrate with an async application framework, the blocking model becomes less attractive.

Common Pitfalls

A common mistake is calling basic_get in a tight polling loop and expecting good worker performance. That underuses RabbitMQ's push model and adds unnecessary overhead.

Another is enabling auto_ack=True for work that can fail midway. That can lose messages.

Developers also sometimes forget that start_consuming() blocks the current thread. If the rest of the program must keep running independently, you need a different thread or a different architecture.

Summary

  • 'BlockingConnection is Pika's straightforward blocking API.'
  • 'basic_get is a pull-style, one-message-at-a-time pattern.'
  • 'basic_consume plus start_consuming() is the usual blocking worker design.'
  • Manual acknowledgements are safer than auto_ack=True for real processing.
  • 'basic_qos(prefetch_count=1) improves fairness across workers.'
  • Blocking consumers are best for dedicated workers, not for highly concurrent async applications.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.