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