簡體   English   中英

Python線程:我可以同時在兩個threading.Event()上睡覺嗎?

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

如果我有兩個threading.Event()對象,並希望睡眠,直到其中任何一個設置為止,是否有一種有效的方法在python中執行此操作? 顯然,我可以使用輪詢/超時做一些事情,但我想讓線程一直處於休眠狀態,直到設置為一個,類似於如何將select用於文件描述符。

所以在下面的實現中, wait_for_either的高效非輪詢實現會是什么樣的?

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

wait_for_either(a, b)

這是一個非輪詢的非過度線程解決方案:修改現有的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

樣品用法:

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_for_either函數,雖然我編寫代碼的方式,最好制作和傳遞or_event 請注意,不應手動設置或清除or_event

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

一種解決方案( 使用輪詢 )將在循環中對每個Event執行順序等待

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

我認為如果你足夠好地調整超時,結果就可以了。


我能想到的最好的非輪詢是在一個不同的線程中等待每一個,並設置一個共享的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()

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

我認為標准庫為這個問題提供了一個非常規范的解決方案,我在這個問題中沒有看到: 條件變量 您的主線程在條件變量上等待,並在每次通知時輪詢事件集。 只有在其中一個事件更新時才會通知它,因此沒有浪費的輪詢。 這是一個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")

示例輸出:

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

請注意,沒有額外的線程或不必要的輪詢,您可以等待任意謂詞變為真(例如,對於要設置的事件的任何特定子集)。 還有一個wait_for包裝器用於while (pred): cond.wait()模式,它可以使您的代碼更容易閱讀。

啟動額外的線程似乎是一個明確的解決方案,但不是很有效。 函數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)

擴展Claudiu的答案,您可以等待:

  1. 事件1或事件2
  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)

示例用法與以前非常相似

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

這是一個老問題,但我希望這可以幫助來自Google的人。
接受的答案相當陳舊,將導致兩次“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())

不漂亮,但你可以使用兩個額外的線程來復用事件......

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_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