简体   繁体   English

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

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

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? 如果我有两个threading.Event()对象,并希望睡眠,直到其中任何一个设置为止,是否有一种有效的方法在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. 显然,我可以使用轮询/超时做一些事情,但我想让线程一直处于休眠状态,直到设置为一个,类似于如何将select用于文件描述符。

So in the following implementation, what would an efficient non-polling implementation of wait_for_either look like? 所以在下面的实现中, wait_for_either的高效非轮询实现会是什么样的?

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

wait_for_either(a, b)

Here is a non-polling non-excessive thread solution: modify the existing Event s to fire a callback whenever they change, and handle setting a new event in that callback: 这是一个非轮询的非过度线程解决方案:修改现有的Event以在它们发生更改时触发回调,并处理在该回调中设置新事件:

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 . 编辑:哦,这是你的wait_for_either函数,虽然我编写代码的方式,最好制作和传递or_event Note that the or_event shouldn't be set or cleared manually. 请注意,不应手动设置或清除or_event

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

One solution ( with polling ) would be to do sequential waits on each Event in a loop 一种解决方案( 使用轮询 )将在循环中对每个Event执行顺序等待

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. 我能想到的最好的非轮询是在一个不同的线程中等待每一个,并设置一个共享的Event ,你将在主线程中等待。

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: 非常有趣,所以我写了以前解决方案的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()

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: 这是一个Python 3示例:

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 (eg 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. 还有一个wait_for包装器用于while (pred): cond.wait()模式,它可以使您的代码更容易阅读。

Starting extra threads seems a clear solution, not very effecient though. 启动额外的线程似乎是一个明确的解决方案,但不是很有效。 Function wait_events will block util any one of events is set. 函数wait_events将阻止util设置任何一个事件。

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)

Extending Claudiu's answer where you can either wait for: 扩展Claudiu的答案,您可以等待:

  1. event 1 OR event 2 事件1或事件2
  2. event 1 AND even 2 事件1甚至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')

This is an old question, but I hope this helps someone coming from Google. 这是一个老问题,但我希望这可以帮助来自Google的人。
The accepted answer is fairly old and will cause an infinite loop for twice-"orified" events. 接受的答案相当陈旧,将导致两次“orified”事件的无限循环。

Here is an implementation using concurrent.futures 这是使用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())

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). 辅助线程(a_thread和b_thread)应该在尝试设置标志时锁定同步,然后应该杀死另一个线程(如果消耗了该线程的事件,可能会重置该事件)。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM