2
votes

I have a thread that is polling on a ZMQ Poller:

poller.poll(timeout)

This thread is also the one which receives and sends back messages over the sockets registered in the poller.

Then I have another thread that may, eventually, create a new socket and register it for polling on input events:

socket = context.socket(...)
socket.bind/connect(...)
poller.register(socket, zmq.POLLIN)

Once the socket is registered, the latter thread will not touch it again.

Is this safe?

Update

The answers/comments I got were about how I should not be doing this. Or which are The Guide's recommendations (which I already knew). But that does not really answer my question.

To be more specific, I would say that I am working with pyzmq Python bindings for ZeroMQ.

Now, although ZeroMQ sockets are not thread safe, it is indeed possible to transfer them from one thread to another as long as there is a full memory barrier during the transfer.

So the first question would be: do I need to set an explicit memory barrier in there? Note that there is one thread that creates and binds/connects the socket and then it registers it, but it will not be using that thread again. Is there an actual conflict? could there be a moment in which I should be explicitly preventing access to the socket from both threads?

Then the second question would be: is registering a socket in a poller thread-safe? Most of the time the thread that performs the polling is busy doing other stuff, but it could happen that it is polling waiting for a timeout. In that case, do I need to use a lock to prevent concurrent access to the poller? or is it safe to register the new socket in the poller while the other thread is polling it?

Update II

I am using Pyro4 to handle and configure remote processes (i.e.: their ZeroMQ connections and their behavior). The initial configuration can be done with the Pyro Proxy very esaily. However, when I start the process, I am in fact running the main loop with a dedicated thread (Pyro oneway call) that keeps running, but if I access the object with the Pyro Proxy again, then this access is from another thread.

So the idea is to avoid modifying the remote object's class but still allow the use of Pyro for configuring the remote objects even when they are running. As long as the creation + binding/connecting + registering of new sockets is safe from another thread, I am good.

1
Why would you use a separate thread that creates a socket? ZMQ sockets aren't thread safe. Your thread that does the polling should receive a message which tells it to create another socket and add it to polling or event loop.Mjh
@Mjh: they are not thread safe, but they can be transferred from one to another (which is what I am doing). I wonder if the way I presented for the transfer needs extra tricks for ensuring memory protection during the transfer. I updated my question with more information and I hope it is a bit more clear now. :-)Peque
Post-Update: It would be fair and reasonable to post the whole MCVE context of the situation that you wish to experiment with. Also a quantitatively stated details should be added so as to complete how tight scheduling granularity ( ns, us, ms, s ) of poller / other blocking activities do you expect at what scales ( n-threads, message-flow congestions, buffer-watermark management practices et al )user3666197
The solution you have in mind is not the hammer you require for this task. You want to create a socket in one thread and "transfer" it to another thread. Why? What problem do you solve doing that? We can discuss thread safety later on after we get to the bottom of your problem - why would you use threads to do this in the first place?Mjh
@Mjh: updated again. Thanks. :-)Peque

1 Answers

2
votes

Once the socket is registered, the latter thread will not touch it again. Is this safe?

No.

Industries that not only require safe solutions, but also export the responsibility to actually prove both the stable and warranted system behaviour to the vendor side (be it due to wise grandfathers, a deep belief in QA/TQM or due to regulations imposed on MIL/GOV/aerospace/healthcare/pharma/automotive et al segment vendor management) would simply straight reject.

Why?

" ... will not touch it again." is just a promise.

Safety cross-validated system design does not settle with less than a proof of a collision avoidance.


Let me cite from a lovely book from Pieter HINTJENS "Code Connected, Vol.1" - a must read piece for ZeroMQ:

Some widely used models, despite being the basis for entire industries, are fundamentally broken, and shared state concurrency is one of them. Code that wants to scale without limit does it like the Internet does, by sending messages and sharing nothing except a common contempt for broken programming models.

You should follow some rules to write happy multithreaded code with ØMQ:

• Isolate data privately within its thread and never share data in multiple threads. The only exception to this are ØMQ contexts, which are threadsafe.
• Stay away from the classic concurrency mechanisms like as mutexes, critical sections, semaphores, etc. These are an anti-pattern in ØMQ applications.
• Create one ØMQ context at the start of your process, and pass that to all threads that you want to connect via inproc sockets.
• Use attached threads to create structure within your application, and connect these to their parent threads using PAIR sockets over inproc. The pattern is: bind parent socket, then create child thread which connects its socket.
• Use detached threads to simulate independent tasks, with their own contexts. Connect these over tcp. Later you can move these to stand-alone processes without changing the code significantly.
• All interaction between threads happens as ØMQ messages, which you can define more or less formally.
Don’t share ØMQ sockets between threads. ØMQ sockets are not threadsafe. Technically it’s possible to migrate a socket from one thread to another but it demands skill. The only place where it’s remotely sane to share sockets between threads are in language bindings that need to do magic like garbage collection on sockets.

If you need to start more than one proxy in an application, for example, you will want to run each in their own thread. It is easy to make the error of creating the proxy frontend and backend sockets in one thread, and then passing the sockets to the proxy in another thread. This may appear to work at first but will fail randomly in real use. Remember: Do not use or close sockets except in the thread that created them.

If you follow these rules, you can quite easily build elegant multithreaded applications, and later split off threads into separate processes as you need to. Application logic can sit in threads, processes, or nodes: whatever your scale needs.

ØMQ uses native OS threads rather than virtual “green” threads. The advantage is that you don’t need to learn any new threading API, and that ØMQ threads map cleanly to your operating system. You can use standard tools like Intel’s ThreadChecker to see what your application is doing. The disadvantages are that native threading APIs are not always portable, and that if you have a huge number of threads (in the thousands), some operating systems will get stressed.


If you’re sharing sockets across threads, don’t. It will lead to random weirdness, and crashes.


We could assume "light" conditions: system not stressed, high-watermark never reached, no big congestions. There is just a single thread running the application (polling and executing tasks on input). So most of the time (99.99%) there is no concurrency. Now, concurrency only occurs when a second thread appears just to add a socket to the pool. There will never be more than 2 threads being executed. And the second thread will be always restricted to adding new sockets to the pool (once added the socket is transferred to the main thread). Is this enough for boundary conditions? – Peque

The more the schematic use-case details were added in update-II, the professional solution shall not lose time and shall avoid any hidden risks by using thread-clean design.

#T1 a poller-maintainer -has Context() instance control
                        -has graceful .close() + .term() responsibility
                        -has POLLER instance under it's own control
                        -has PAIR  .bind(    "inproc://worker2poller" )
                        -has PAIR  .recv() <add_socket>-request processing responsibility

#T2 a worker-process:   -has PAIR  .connect( "inproc://worker2poller" )
                        -has PAIR  .send() privilege to ask T1 to add a socket & include it into POLLER

While GIL anyway avoids any chance to find the python threads run PARALLEL, the pure OOP-design is the motivation to keep the architecture with both clean and separated responsibilities and keeping the Formal Communication Patterns fully scaleable.