It is impossible to correctly wait for multiple queues in Python and only consume the first element of any of them.
This came as a big surprise to me. Both because it seems like an operation that should be supported, but also because this never had been a problem in practice.
While reading up on the programming language Go, I enjoyed the
primitives they provide for concurrency. The three basic concepts are
go operator which calls a function in a new thread, the typed
channel which can be used to transfer values between threads, and an
extension of the
switch statement which can be used to wait for
values on channels.
To play around a bit with the concepts, I wanted to port these to
go using Python’s
threading.Thread and its
target argument is
channel is nothing but good old
queue.Queue. But the extended
statement stumped me.
What I’d need would be a function
which returns a tuple
(channel, obj) representing the
channel which had the first contents, and the object read from it.
Furthermore, no other channels should have an object removed from
Short of busy-polling each queue in turn, there is no way of implementing this.
I found some discussion on how to do this for
multiprocessing.Queue which utilizes the fact that it is implemented
using file descriptors, where we can use
thread.Lock as the underlying mechanism to ensure thread
safety, and there is no way of waiting for more than one lock in a
A workaround would be to start one polling thread per queue to wait
on, and have each of them write the
(queue, obj) tuple to a common
queue, and the main thread then simply waits on that queue. This would
solve the first part of the problem, but it would also mean that the
original queues can get objects read from them even when another queue
already produced an object.
The funny thing is, as I mentioned, that this never was a problem in practice for me. It seems that when designing message-passing systems, the idea that a thread has one and exactly one “inbox” for messages is quite natural and works well. I’d love to see some concrete examples of Go applications using this feature, and what design decisions encourage this.