Python threading: can I sleep on two threading.Event()s simultaneously?

Question:

If I have two threading.Event() objects, and wish to sleep until either one of them is set, is there an efficient way to do that in python? Clearly I could do something with polling/timeouts, but I would like to really have the thread sleep until one is set, akin to how select is used for file descriptors.

So in the following implementation, what would an efficient non-polling implementation of wait_for_either look like?

a = threading.Event()
b = threading.Event()

wait_for_either(a, b)
Asked By: kdt

||

Answers:

One solution (with polling) would be to do sequential waits on each Event in a loop

def wait_for_either(a, b):
    while True:
        if a.wait(tunable_timeout):
            break
        if b.wait(tunable_timeout):
            break

I think that if you tune the timeout well enough the results would be OK.


The best non-polling I can think of is to wait for each one in a different thread and set a shared Event whom you will wait after in the main thread.

def repeat_trigger(waiter, trigger):
    waiter.wait()
    trigger.set()

def wait_for_either(a, b):
    trigger = threading.Event()
    ta = threading.Thread(target=repeat_trigger, args=(a, trigger))
    tb = threading.Thread(target=repeat_trigger, args=(b, trigger))
    ta.start()
    tb.start()
    # Now do the union waiting
    trigger.wait()

Pretty interesting, so I wrote an OOP version of the previous solution:

class EventUnion(object):
    """Register Event objects and wait for release when any of them is set"""
    def __init__(self, ev_list=None):
        self._trigger = Event()
        if ev_list:
            # Make a list of threads, one for each Event
            self._t_list = [
                Thread(target=self._triggerer, args=(ev, ))
                for ev in ev_list
            ]
        else:
            self._t_list = []

    def register(self, ev):
        """Register a new Event"""
        self._t_list.append(Thread(target=self._triggerer, args=(ev, )))

    def wait(self, timeout=None):
        """Start waiting until any one of the registred Event is set"""
        # Start all the threads
        map(lambda t: t.start(), self._t_list)
        # Now do the union waiting
        return self._trigger.wait(timeout)

    def _triggerer(self, ev):
        ev.wait()
        self._trigger.set()
Answered By: Iulius Curt

Not pretty, but you can use two additional threads to multiplex the events…

def wait_for_either(a, b):
  flag = False #some condition variable, event, or similar

  class Event_Waiter(threading.Thread):
    def __init__(self, event):
        self.e = event
    def run(self):
        self.e.wait()
        flag.set()

  a_thread = Event_Waiter(a)
  b_thread = Event_Waiter(b)
  a.start()
  b.start()
  flag.wait()

Note, you may have to worry about accidentally getting both events if they arrive too quickly. The helper threads (a_thread and b_thread) should lock synchronize around trying to set flag and then should kill the other thread (possibly resetting that thread’s event if it was consumed).

Answered By: sshannin

Here is a non-polling non-excessive thread solution: modify the existing Events to fire a callback whenever they change, and handle setting a new event in that callback:

import threading

def or_set(self):
    self._set()
    self.changed()

def or_clear(self):
    self._clear()
    self.changed()

def orify(e, changed_callback):
    e._set = e.set
    e._clear = e.clear
    e.changed = changed_callback
    e.set = lambda: or_set(e)
    e.clear = lambda: or_clear(e)

def OrEvent(*events):
    or_event = threading.Event()
    def changed():
        bools = [e.is_set() for e in events]
        if any(bools):
            or_event.set()
        else:
            or_event.clear()
    for e in events:
        orify(e, changed)
    changed()
    return or_event

Sample usage:

def wait_on(name, e):
    print "Waiting on %s..." % (name,)
    e.wait()
    print "%s fired!" % (name,)

def test():
    import time

    e1 = threading.Event()
    e2 = threading.Event()

    or_e = OrEvent(e1, e2)

    threading.Thread(target=wait_on, args=('e1', e1)).start()
    time.sleep(0.05)
    threading.Thread(target=wait_on, args=('e2', e2)).start()
    time.sleep(0.05)
    threading.Thread(target=wait_on, args=('or_e', or_e)).start()
    time.sleep(0.05)

    print "Firing e1 in 2 seconds..."
    time.sleep(2)
    e1.set()
    time.sleep(0.05)

    print "Firing e2 in 2 seconds..."
    time.sleep(2)
    e2.set()
    time.sleep(0.05)

The result of which was:

Waiting on e1...
Waiting on e2...
Waiting on or_e...
Firing e1 in 2 seconds...
e1 fired!or_e fired!

Firing e2 in 2 seconds...
e2 fired!

This should be thread-safe. Any comments are welcome.

EDIT: Oh and here is your wait_for_either function, though the way I wrote the code, it’s best to make and pass around an or_event. Note that the or_event shouldn’t be set or cleared manually.

def wait_for_either(e1, e2):
    OrEvent(e1, e2).wait()
Answered By: Claudiu

Starting extra threads seems a clear solution, not very effecient though.
Function wait_events will block util any one of events is set.

def wait_events(*events):
    event_share = Event()

    def set_event_share(event):
        event.wait()
        event.clear()
        event_share.set()
    for event in events:
        Thread(target=set_event_share(event)).start()

    event_share.wait()

wait_events(event1, event2, event3)
Answered By: whenov
def wait_for_event_timeout(*events):
    while not all([e.isSet() for e in events]):
        #Check to see if the event is set. Timeout 1 sec.
        ev_wait_bool=[e.wait(1) for e in events]
        # Process if all events are set. Change all to any to process if any event set
        if all(ev_wait_bool):
            logging.debug('processing event')
        else:
            logging.debug('doing other work')


e1 = threading.Event()
e2 = threading.Event()

t3 = threading.Thread(name='non-block-multi',
                      target=wait_for_event_timeout,
                      args=(e1,e2))
t3.start()

logging.debug('Waiting before calling Event.set()')
time.sleep(5)
e1.set()
time.sleep(10)
e2.set()
logging.debug('Event is set')
Answered By: Sud

Extending Claudiu’s answer where you can either wait for:

  1. event 1 OR event 2
  2. event 1 AND even 2

from threading import Thread, Event, _Event

class ConditionalEvent(_Event):
    def __init__(self, events_list, condition):
        _Event.__init__(self)

        self.event_list = events_list
        self.condition = condition

        for e in events_list:
            self._setup(e, self._state_changed)

        self._state_changed()

    def _state_changed(self):
        bools = [e.is_set() for e in self.event_list]
        if self.condition == 'or':                
            if any(bools):
                self.set()
            else:
                self.clear()

        elif self.condition == 'and':                 
            if all(bools):
                self.set()
            else:
                self.clear()

    def _custom_set(self,e):
        e._set()
        e._state_changed()

    def _custom_clear(self,e):
        e._clear()
        e._state_changed()

    def _setup(self, e, changed_callback):
        e._set = e.set
        e._clear = e.clear
        e._state_changed = changed_callback
        e.set = lambda: self._custom_set(e)
        e.clear = lambda: self._custom_clear(e)

Example usage will be very similar as before

import time

e1 = Event()
e2 = Event()

# Example to wait for triggering of event 1 OR event 2
or_e = ConditionalEvent([e1, e2], 'or')

# Example to wait for triggering of event 1 AND event 2
and_e = ConditionalEvent([e1, e2], 'and')
Answered By: arsanious

This is an old question, but I hope this helps someone coming from Google.
The accepted answer is fairly old and will cause an infinite loop for twice-“orified” events.

Here is an implementation using concurrent.futures

import concurrent.futures
from concurrent.futures import ThreadPoolExecutor

def wait_for_either(events, timeout=None, t_pool=None):
    '''blocks untils one of the events gets set

    PARAMETERS
    events (list): list of threading.Event objects
    timeout (float): timeout for events (used for polling)
    t_pool (concurrent.futures.ThreadPoolExecutor): optional
    '''

    if any(event.is_set() for event in events):
        # sanity check
        pass
    else:
        t_pool = t_pool or ThreadPoolExecutor(max_workers=len(events))
        tasks = []
        for event in events:
            tasks.append(t_pool.submit(event.wait))

        concurrent.futures.wait(tasks, timeout=timeout, return_when='FIRST_COMPLETED')
        # cleanup
        for task in tasks:
            try:
                task.result(timeout=0)
            except concurrent.futures.TimeoutError:
                pass

Testing the function

import threading
import time
from datetime import datetime, timedelta

def bomb(myevent, sleep_s):
    '''set event after sleep_s seconds'''
    with lock:
        print('explodes in ', datetime.now() + timedelta(seconds=sleep_s))
    time.sleep(sleep_s)
    myevent.set()
    with lock:
        print('BOOM!')

lock = threading.RLock()  # so prints don't get jumbled
a = threading.Event()
b = threading.Event()

t_pool = ThreadPoolExecutor(max_workers=2)

threading.Thread(target=bomb, args=(event1, 5), daemon=True).start()
threading.Thread(target=bomb, args=(event2, 120), daemon=True).start()

with lock:
    print('1 second timeout, no ThreadPool', datetime.now())

wait_for_either([a, b], timeout=1)

with lock:
    print('wait_event_or done', datetime.now())
    print('=' * 15)

with lock:
    print('wait for event1', datetime.now())

wait_for_either([a, b], t_pool=t_pool)

with lock:
    print('wait_event_or done', datetime.now())
Answered By: Lance

I think the standard library provides a pretty canonical solution to this problem that I don’t see brought up in this question: condition variables. You have your main thread wait on a condition variable, and poll the set of events each time it is notified. It is only notified when one of the events is updated, so there is no wasteful polling. Here is a Python 3 example:

from threading import Thread, Event, Condition
from time import sleep
from random import random

event1 = Event()
event2 = Event()
cond = Condition()

def thread_func(event, i):
    delay = random()
    print("Thread {} sleeping for {}s".format(i, delay))
    sleep(delay)

    event.set()
    with cond:
        cond.notify()

    print("Thread {} done".format(i))

with cond:
    Thread(target=thread_func, args=(event1, 1)).start()
    Thread(target=thread_func, args=(event2, 2)).start()
    print("Threads started")

    while not (event1.is_set() or event2.is_set()):
        print("Entering cond.wait")
        cond.wait()
        print("Exited cond.wait ({}, {})".format(event1.is_set(), event2.is_set()))

    print("Main thread done")

Example output:

Thread 1 sleeping for 0.31569427100177794s
Thread 2 sleeping for 0.486548134317051s
Threads started
Entering cond.wait
Thread 1 done
Exited cond.wait (True, False)
Main thread done
Thread 2 done

Note that wit no extra threads or unnecessary polling, you can wait for an arbitrary predicate to become true (e.g. for any particular subset of the events to be set). There’s also a wait_for wrapper for the while (pred): cond.wait() pattern, which can make your code a bit easier to read.

Answered By: Brendan
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.