虽然仔细检查threading.Condition
是否正确修补了猴子,但我注意到,monkeypatched threading.Thread(…).start()
的行为与gevent.spawn(…)
的行为不同。
考虑:
from gevent import monkey; monkey.patch_all()
from threading import Thread, Condition
import gevent
cv = Condition()
def wait_on_cv(x):
cv.acquire()
cv.wait()
print "Here:", x
cv.release()
# XXX: This code yields "This operation would block forever" when joining the first thread
threads = [ gevent.spawn(wait_on_cv, x) for x in range(10) ]
"""
# XXX: This code, which seems semantically similar, works correctly
threads = [ Thread(target=wait_on_cv, args=(x, )) for x in range(10) ]
for t in threads:
t.start()
"""
cv.acquire()
cv.notify_all()
print "Notified!"
cv.release()
for x, thread in enumerate(threads):
print "Joining", x
thread.join()
请注意,特别是以XXX
开头的两条评论。
使用第一行(gevent.spawn
)时,第一行thread.join()
会引发异常:
Notified! Joining 0 Traceback (most recent call last): File "foo.py", line 30, in thread.join() File "…/gevent/greenlet.py", line 291, in join result = self.parent.switch() File "…/gevent/hub.py", line 381, in switch return greenlet.switch(self) gevent.hub.LoopExit: This operation would block forever
但是,Thread(…).start()
(第二个块),一切都按预期工作。
为什么会这样? gevent.spawn()
和Thread(…).start()
之间的区别是什么?
答案 0 :(得分:5)
您的代码中发生的事情是您在threads
列表中创建的 greenlets 还没有机会被执行,因为gevent
不会触发一个上下文切换,直到您使用gevent.sleep()
在代码中明确地执行此操作,或者通过调用阻止例如的函数隐式地执行此操作semaphore.wait()
或屈服等......,看看您可以在cv.wait()
之前插入一个打印件,并看到只有在调用cv.notify_all()
之后才会调用它:
def wait_on_cv(x):
cv.acquire()
print 'acquired ', x
cv.wait()
....
因此,在创建 greenlets 列表后,对代码的一个简单修复就是插入一些会触发上下文切换的内容,例如:
...
threads = [ gevent.spawn(wait_on_cv, x) for x in range(10) ]
gevent.sleep() # Trigger a context switch
...
注意:我还是gevent
的新手,所以我不知道这是否是正确的做法:)
通过这种方式,所有 greenlets 都有机会被执行,并且当他们调用cv.wait()
时,每个人都会触发上下文切换,同时他们将会
将他们自己注册到条件服务员,以便在cv.notify_all()
被调用时
将通知所有 greenlets 。
HTH,