Search code examples
multithreadingasynchronouszeromqdistributed-computingdistributed-system

ZeroMQ: How to handle non-message-related, asynchronous events in a ZeroMQ node?


Assume I have a node (process, thread, whatever) with a ZeroMQ interface, let's say a REP socket. This means I have an infinite main loop, which sleeps in the zmq_recv or zmq_poll function.

Now that node should also receive data from another asynchronous event. Imagine for example a keyboard button-press event or toggling a GPIO pin or a timer expiration. The execution must also sleep while waiting for these events.

How should the main loop be written, such that it wakes up on either type of event, namely the reception of a message and the occurrence of the asynchronous event?

I can think of two solutions:

First, a polling loop, where both events are checked in a non-blocking way, and I run the loop every couple of milliseconds. This seems non-ideal in terms of processing load.

Second, I move the blocking code for both events into separate threads. In the main loop instead, I sleep on a semaphore (or condition variable), which is posted by the occurrence of either event. That's the way I would go in a traditional application, but the ZeroMQ Guide written by Pieter Hintjens is very explicit on not using semaphores:

Stay away from the classic concurrency mechanisms like as mutexes, critical sections, semaphores, etc. These are an anti-pattern in ZeroMQ applications.

So what to do? What's the best practice here?


Solution

  • Depending on your OS, most events in the system are generated through some file descriptor becoming ready to read. This is generally how it works on Linuxs, Unixes, etc. For instance, keyboard input comes in through STDIN. And of course, file descriptors of any description can be included in a ZMQ poll.

    Where an event isn't raised through a file descriptor that can be used in ZMQ poll (e.g. a serial port on Windows becoming ready to read), I generally use a thread to translate the event into a message sent through a ZMQ socket. Works well enough. Becomes non-portable, but that's unavoidable.

    GPIO can be harder. If it's not backed by some driver that's got an ISR integrated into the OS's driver stack, then you're going to have to poll it.