为什么`gevent.spawn`与monkeypatched`threading.Thread()`不同?

时间:2012-10-23 22:43:26

标签: python multithreading gevent

虽然仔细检查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()之间的区别是什么?

1 个答案:

答案 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,