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