Python线程:我可以同时在两个threading.Event()上睡觉吗?

Python线程:我可以同时在两个threading.Event()上睡觉吗?,python,multithreading,Python,Multithreading,如果我有两个threading.Event()对象,并且希望在其中一个被设置之前一直处于休眠状态,那么在python中有没有一种有效的方法来实现这一点?显然,我可以对轮询/超时进行一些处理,但我希望在设置一个线程之前真正让线程休眠,这类似于select如何用于文件描述符 那么在下面的实现中,wait\u for\u的高效非轮询实现是什么样子的呢 a = threading.Event() b = threading.Event() wait_for_either(a, b) 一种解决方案(使

如果我有两个
threading.Event()
对象,并且希望在其中一个被设置之前一直处于休眠状态,那么在python中有没有一种有效的方法来实现这一点?显然,我可以对轮询/超时进行一些处理,但我希望在设置一个线程之前真正让线程休眠,这类似于
select
如何用于文件描述符

那么在下面的实现中,
wait\u for\u的高效非轮询实现是什么样子的呢

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

wait_for_either(a, b)
一种解决方案(使用轮询)是对循环中的每个
事件执行顺序等待

def wait_for_either(a, b):
    while True:
        if a.wait(tunable_timeout):
            break
        if b.wait(tunable_timeout):
            break
我认为如果你把超时调整得足够好,结果就可以了


我能想到的最好的非轮询方法是在不同的线程中等待每个事件,并在主线程中设置一个共享的
事件

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()

非常有趣,因此我编写了前一个解决方案的OOP版本:

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()

不太好,但是您可以使用另外两个线程来多路传输事件

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()

注意,如果这两个事件到达得太快,您可能不得不担心它们会意外地同时发生。助手线程(a_线程和b_线程)应该锁定同步以尝试设置标志,然后应该杀死另一个线程(如果该线程被消耗,可能会重置该线程的事件)

这里有一个非轮询非过量线程解决方案:修改现有的
事件
s,以便在它们发生更改时触发回调,并处理在该回调中设置新事件:

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
示例用法:

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)
其结果是:

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!
这应该是线程安全的。欢迎提出任何意见

编辑:哦,这是你的
wait\u for\u other
函数,尽管我写代码的方式是,最好制作并传递
或\u事件。请注意,不应手动设置或清除
或\u事件

def wait_for_either(e1, e2):
    OrEvent(e1, e2).wait()

开始额外的线程似乎是一个明确的解决方案,但不是很有效。 函数wait_events将阻止设置任何一个事件

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)
将答案扩展到您可以等待的位置:

  • 事件1或事件2
  • 事件1和偶数2

  • 示例用法将与以前非常相似

    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')
    

    这是一个老问题,但我希望这对来自谷歌的人有所帮助。
    公认的答案相当古老,将导致两次“orified”事件的无限循环

    下面是一个使用
    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
    
    测试功能

    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())
    

    我认为标准库为这个问题提供了一个非常规范的解决方案,但我在这个问题中没有看到。让主线程等待条件变量,并在每次收到通知时轮询事件集。它只在其中一个事件被更新时被通知,因此没有浪费轮询。下面是一个Python 3示例:

    从线程导入线程、事件、条件
    从时间上导入睡眠
    从随机导入随机
    event1=Event()
    event2=事件()
    cond=条件()
    def thread_func(事件,i):
    延迟=随机()
    打印(“线程{}为{}s休眠”。格式(i,延迟))
    睡眠(延迟)
    event.set()
    有条件:
    条件通知
    打印(“线程{}完成”。格式(i))
    有条件:
    线程(target=Thread_func,args=(event1,1)).start()
    线程(target=Thread_func,args=(event2,2)).start()
    打印(“线程已启动”)
    而不是(event1.is_set()或event2.is_set()):
    打印(“输入条件等待”)
    等等
    打印(“已退出cond.wait({},{})”.format(event1.is_set(),event2.is_set())
    打印(“主线程完成”)
    
    示例输出:

    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
    

    请注意,在没有额外线程或不必要的轮询的情况下,您可以等待任意谓词变为true(例如,设置事件的任何特定子集)。对于
    while(pred):cond.wait()
    模式,还有一个
    wait\u for
    wrapper,它可以使代码更易于阅读。

    使用两个不同的事件而不使用同一个事件有什么好的理由吗?@Iulius您有一个想要被事件驱动的线程,但有两个队列。。。所以当q或q得到一个项目时,你需要醒来。我很惊讶Python没有这个内置的。你可以让repeat_trigger也检查触发器(对于trigger,timeout=0,对于waiter,timeout>0),这样所有线程最终都会结束,我的想法是一样的,但肯定有比启动两个线程更好的方法…这很好!但是,我发现了一个问题:如果您两次对同一事件执行orify,那么无论何时设置或清除它,都会得到一个无限循环。非常感谢!这正是我要找的。您是否同意在开放源码许可条款下使用此答案中的代码?BSD或MIT将是理想的选择,因为它们与Numpy、Pandas、Scipy等兼容。@Naitsirch:当然没问题:)随心所欲地处理它这确实很好,但依赖于修改事件的私有实现,因此如果在未来的版本中实现发生更改,将会严重中断。不仅如此,当我试图用e2初始化一个事件e1,然后用事件e3初始化它时,它被打断了。或者简单地通过事件e2两次起源。这是因为您修改了现有的事件。如果知道触发了哪一个事件会很好。如果您的
    Thread()
    不正确,请在创建Thread之前调用该函数,我认为如果您等待大量事件(十),这可能不可靠,由于ThreadPoolExecutor的max_workers参数指定了要启动的最大线程数,因此您可能会得到较少的线程数,在这种情况下,某些事件不会等待。此外,执行器在其所有线程退出之前不会结束。应该在“with”语句中使用Executors,如果您尝试这样做,您会发现它会死锁,直到触发所有事件。每次调用代码时,它都会“泄漏”正在运行的线程。什么是
    \u Event
    对象?直接利用
    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())
    
    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