Search code examples
c++multithreadingzeromqatomicsetsockopt

How to efficiently pass an argument, just very occasionally met, to a child thread?


I'd like to pass a string argument to a child thread ( that is continuously reading a socket ) and call a setsockopt() with that argument on that socket.

I'm using ZeroMQ sockets, so calling setsockopt() is not threadsafe here, I'd call the setsockopt() from the child thread ( as was recommended here ). An argument update would occur probably only once in billions of read cycles, and it feels a bit wrong to add an if-structure to the child's every cycle like this:

bool new_arg_available;
std::string new_arg;

while(1){
    sub_socket->recv(data);   // . . . . . . a blocking method call
    printData(data)

    ...                       // . . . . . . data can set new_arg_available

    if (new_arg_available){   // . . . . . . synchronization goes here
        sub_socket->setsockopt(ZMQ_SUBSCRIBE, new_arg, ... );
        new_arg_available = false;
    }
}

For me, the most straightforward would probably be either:

  1. Add a mutex to the global namespace, lock it when a new_arg available in the parent thread, and unlock it from the child thread.

  2. use a std::atomic<bool> and use it the same way as in 1.

However, what I would like to achieve somehow is to make the child interruptible, so that I could eliminate the if-structure from the end of the while(){...} block. I wouldn't mind the penalty of a context switch, because this event would be so seldom.

I'm relatively new to C++, and I would like to learn about best practices here, how to achieve this in an efficient way. I would like to solve it without using Boost, the only examples I could find to achieve interruptible threads were using Boost.


Solution

  • Welcome to the land of Zen-of-Zero

    The primary remark on the ZeroMQ evangelisation is, that the ZeroMQ authors have advocated since ever to avoid any sort of sharing - Zero-sharing.

    Given you already have instantiated the ZeroMQ infrastructure, better use an inter-thread PUSH/PULL over the IO-thread-less inproc:// transport-class and forget about any tricky (b-)locking.

    The one side ( the Injector ) just aPushCHANNEL.send( new_arg );
    and the other ( the Implementor ) just either aPullCHANNEL.recv()-s as needed / when needed, or may use smarter means of aPullCHANNEL.poll()-testing, again best if using the Zero-wait form of the polling-test if aPullCHANNEL.poll( 0 ){...}else{...}

    And your architecture becomes a fully decomposed, multi-agent role-based clean and smart, without having any dirty deadlocks ( so best forget REQ/REP since beginning ), without any hard to trace/debug conceptual flaws and all errors get detected where applicable and where a local responsibility makes sense to map the root-cause of any such error for an on-the-fly due error-handling.


    NB: Yes, ZeroMQ has been since ever designed without a need for a thread-safeness, as there was nothing left to be shared ( Zero-sharing ). Some recent efforts in API v4.1+ started to decline from this Zen-of-Zero, so one may read about some aspects of thread-safeness, yet as noted above and as present on almost every page of the fabulous Pieter HINTJENS' ZeroMQ bible-book - "Code Connected, Volume 1" - best share nothing.

    The ZeroMQ design is fully asynchronous and requires no care for locking, mutex-gymnastics and other forms of extrinsically introduced elements in an otherwise clean and smart multi-agent async signalling / messaging based architecture. You will fall in love with it, so keep trying and read the book. It will be hard, yet it will pay a lot for those, who will carry on and find the beauty of ZeroMQ Zen-of-Zero.