Of course! Let's dive deep into zmq.Poller, a powerful and essential tool for building non-blocking, event-driven applications with ZeroMQ.

What is a ZMQ Poller?
A zmq.Poller is a class from the pyzmq library that allows you to monitor multiple ZeroMQ sockets (and other file descriptors) for activity (like incoming messages) without blocking your program.
Think of it like a receptionist who is waiting for a phone to ring on several different lines. Instead of picking up one phone and waiting indefinitely (which would block them from hearing the others), the receptionist can quickly check each line in turn to see if any of them are ringing.
This is the foundation of I/O multiplexing, which is crucial for building scalable, responsive network applications.
Why Use a Poller? (The Problem it Solves)
Without a poller, your ZeroMQ application is typically stuck in one of two modes:

- Blocking (
recv()): Your program callssocket.recv()and waits. If no message is available, your program freezes until one arrives. It can't do anything else in the meantime. - Busy-Looping (Bad Practice): You could do something like
while True: if socket.poll(): msg = socket.recv(). This works, but it's incredibly inefficient. It consumes 100% of a CPU core constantly checking, even when there's no work to do.
The Poller Solution: The poller allows your program to efficiently "sleep" until one or more of the sockets it's monitoring are ready for I/O. This way, your program can remain responsive, use CPU resources wisely, and handle multiple communication channels concurrently.
Core Concepts and Methods
The zmq.Poller class has a simple and intuitive API.
poller.register(socket, flags=zmq.POLLIN)
This is how you tell the poller which socket to watch and what kind of activity to look for.
socket: The ZeroMQ socket you want to monitor.flags: The event to poll for. The most common iszmq.POLLIN, which means "notify me when there is a message to be received."zmq.POLLIN: A message is available to be received.zmq.POLLOUT: The socket is ready to send a message (its send buffer is not full).zmq.POLLERR: An error has occurred on the socket.
You can register multiple sockets with the same poller.

poller.unregister(socket)
Removes a socket from the poller's watch list.
poller.poll(timeout=None)
This is the heart of the poller. It blocks until at least one registered socket is ready, or until the timeout is reached.
- Returns: A list of
(socket, event)tuples for every socket that has activity. If no sockets are ready, it returns an empty list. timeout: The time to wait in milliseconds.- If
timeoutisNone(the default),poll()will block indefinitely until an event occurs. - If
timeoutis0,poll()will perform a non-blocking check and return immediately.
- If
Practical Examples
Let's look at a few common patterns.
Example 1: Basic Single-Direction Polling
This is the simplest use case: a single receiver waiting for messages from a publisher.
import zmq
import time
# 1. Setup the Context and Socket
context = zmq.Context()
receiver = context.socket(zmq.PULL)
receiver.bind("tcp://*:5555") # Bind to a port
print("Receiver: Polling for messages...")
# 2. Create and Register the Poller
poller = zmq.Poller()
poller.register(receiver, zmq.POLLIN)
# 3. Poll indefinitely
while True:
# Wait for up to 1 second for a message
socks = dict(poller.poll(timeout=1000)) # poll() returns a list, we convert to a dict for easy lookup
# Check if our receiver socket has any events
if receiver in socks and socks[receiver] == zmq.POLLIN:
print("Receiver: Message received!")
message = receiver.recv()
print(f"Receiver: Received -> {message.decode()}")
# This part demonstrates that the program isn't blocked
print("Receiver: Doing other work...")
time.sleep(0.5)
To test this, you would run a simple publisher in another terminal:
# In a separate terminal
python -c "
import zmq
context = zmq.Context()
sender = context.socket(zmq.PUSH)
sender.connect('tcp://localhost:5555')
for i in range(5):
sender.send_string(f'Hello Message {i}')
print(f'Sent: Hello Message {i}')
"
Example 2: Handling Multiple Sockets (The Power of Poller)
This is where the poller truly shines. A single thread can manage communication with multiple sockets. Here, a worker will listen for tasks from a PUSH socket and send results to a PUB socket.
import zmq
import time
# Setup Context and Sockets
context = zmq.Context()
# Socket to receive tasks from a ventilator (PUSH)
receiver = context.socket(zmq.PULL)
receiver.connect("tcp://localhost:5557")
# Socket to send results to a sink (PUB)
sender = context.socket(zmq.PUB)
sender.connect("tcp://localhost:5558")
print("Worker: Polling for tasks and ready to send results...")
# Create and Register the Poller
poller = zmq.Poller()
poller.register(receiver, zmq.POLLIN)
# We could also register the sender for POLLOUT if we wanted to be more precise,
# but for this simple example, we'll just send when we have a task.
while True:
# Poll with a timeout to allow for graceful shutdown if needed
socks = dict(poller.poll(timeout=1000))
# Check if there's a task to process
if receiver in socks and socks[receiver] == zmq.POLLIN:
# Receive the task
task = receiver.recv_string()
print(f"Worker: Received task '{task}', processing...")
# Simulate work
time.sleep(1)
# Send the result
result = f"Processed: {task}"
# We use a topic for the PUB/SUB pattern
sender.send_string(f"result1 {result}")
print(f"Worker: Sent result '{result}'")
# You could add other logic here that runs when no sockets are ready
# print("Worker: Idle, waiting for work...")
Example 3: Non-Blocking Send with POLLOUT
Sometimes you want to send messages as fast as possible, but you don't want your program to block if the network is slow and the send buffer is full. Using POLLOUT solves this.
import zmq
import time
context = zmq.Context()
sender = context.socket(zmq.PUB)
sender.connect("tcp://localhost:5558") # Connect to a subscriber
poller = zmq.Poller()
# We register the socket for POLLOUT events
poller.register(sender, zmq.POLLOUT)
print("Sender: Ready to send non-blocking...")
message_count = 0
while True:
# Poll to see if the socket is ready to send
socks = dict(poller.poll(timeout=0)) # Non-blocking poll
if sender in socks and socks[sender] == zmq.POLLOUT:
try:
# Send a message
topic = f"topic.{message_count % 3}"
message = f"Message {message_count}"
sender.send_string(f"{topic} {message}")
message_count += 1
print(f"Sender: Sent '{message}' on topic '{topic}'")
except zmq.ZMQError as e:
# This might happen if the socket is closed unexpectedly
print(f"Sender: Error sending - {e}")
break
# Simulate doing other work
time.sleep(0.1)
Poller vs. zmq.Socket's Built-in poll()
zmq.Poller is the recommended, high-level way to manage polling. However, every zmq.Socket object also has its own poll(timeout) method.
| Feature | zmq.Poller |
socket.poll(timeout) |
|---|---|---|
| Purpose | Monitor multiple sockets and file descriptors. | Monitor a single socket. |
| API | register(), unregister(), poll() |
poll(timeout) |
| Return Value | A list of (socket, event) tuples. |
A bitmask of events (e.g., zmq.POLLIN). You have to check if socket.poll() & zmq.POLLIN is true. |
| Best For | Most applications, especially those with multiple communication channels. | Simple scripts or situations where you only ever need to watch one socket at a time. |
Recommendation: For anything but the simplest scripts, use zmq.Poller. It's cleaner, more scalable, and its API is designed for managing multiple resources.
Summary
The zmq.Poller is an indispensable tool for writing robust, efficient, and non-blocking ZeroMQ applications. By allowing you to wait for activity on multiple sockets simultaneously, it enables you to build high-performance network services and applications that are responsive and make optimal use of system resources.
