Passive waiting is a way of suspending programs execution without consuming any CPU (in contrast with active waiting where a program is busy doing nothing). In Python, you can use certain classes from the threading module (such as Condition and Event). Let’s take for example the Event class. It is good for waiting for an event. But what if you need to wait for many events? You might say „Why not to make a list of multiple Event objects and wait for all of them?” Well… that is not going to work:
import threading, collections
events=[threading.Event() for _ in range(10)] # make 10 events
collections.deque((e.clear() for e in events), 0) # clear all event flags
activated={e for e in events if e.wait(timeout)} # wait for events, get a set of activated ones
There are two problems with this kind of code:
- If you use any timeout value, you degrade it to active wait (and you will also wait until the last event in the list gets activated or times out).
- If you don’t use a timeout value, the code gets stuck on the first event (and then the second, third, etc.).
If you used to program in C/C++, you might want to use a select function. Select allows you to wait for several I/O operations and returns when any of them ends. The good news is there is such a function in Python 3. Concretely, it is the select method of the selectors.BaseSelector class. The bad news is you can wait only on file objects.
I don’t know how about you but I’d like to wait on other things, such as MULTIPLE EVENT OBJECTS! But you can’t, because those are not file objects. Now you might be saying
Do you wish there was a way to wait on those Event objects? There is :-). Here’s a slightly modified code, so it works in Python 3 (the original code is here):
class WaitableEvent:
"""
Provides an abstract object that can be used to resume select loops with
indefinite waits from another thread or process. This mimics the standard
threading.Event interface.
"""
def __init__(self):
self._read_fd, self._write_fd = os.pipe()
def wait(self, timeout=None):
rfds, wfds, efds = select.select([self._read_fd], [], [], timeout)
return self._read_fd in rfds
def isSet(self):
return self.wait(0)
def clear(self):
if self.isSet():
os.read(self._read_fd, 1)
def set(self):
if not self.isSet():
os.write(self._write_fd, b'1')
def fileno(self):
"""
Return the FD number of the read side of the pipe, allows this object to
be used with select.select().
"""
return self._read_fd
def __del__(self):
os.close(self._read_fd)
os.close(self._write_fd)
To wait for multiple event objects using the select method is simple as this:
import selectors
sel = selectors.DefaultSelector() # create selector
event1 = WaitableEvent() # create event 1
event2 = WaitableEvent() # create event 2
sel.register(event1, selectors.EVENT_READ, "event 1")
sel.register(event2, selectors.EVENT_READ, "event 2")
# wait for an event or until the 5s timeout expires
events = sel.select(timeout=5)
if not events:
print("Timeout after 5s")
else:
for key, mask in events:
print(key.data)
Any suggestions or improvements? Leave a comment below :-).

Leave a Reply