Почему `gevent.spawn` отличается от исправленного `threading.Thread()`?

Во время двойной проверки того, что threading.Condition правильно пропатчен, я заметил, что пропатченный 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()?


person David Wolever    schedule 23.10.2012    source источник


Ответы (1)


Что происходит в вашем коде, так это то, что greenlets, которые вы создали в своем threads списке, еще не имели возможности быть выполнены, потому что gevent не вызовет переключение контекста, пока вы не сделаете это явно в своем коде. используя gevent.sleep() и тому подобное или неявно, вызывая функцию, которая блокирует, например semaphore.wait() или с помощью yield и т. д. ..., чтобы увидеть, что вы можете вставить печать перед cv.wait() и увидеть, что она вызывается только после вызова cv.notify_all():

def wait_on_cv(x):
    cv.acquire()
    print 'acquired ', x
    cv.wait()
    ....

Таким образом, простым исправлением вашего кода будет вставка чего-то, что вызовет переключение контекста после того, как вы создадите список зеленых элементов, например:

...
threads = [ gevent.spawn(wait_on_cv, x) for x in range(10) ]
gevent.sleep()  # Trigger a context switch
...

Примечание. Я все еще новичок в gevent, поэтому не знаю, правильный ли это способ :)

Таким образом, у всех гринлетов будет шанс быть выполненным, и каждый из них вызовет переключение контекста при вызове cv.wait(), а тем временем они сами зарегистрируют себя в ожидающих условиях, чтобы, когда cv.notify_all(), он уведомит все зеленые элементы.

ХТХ,

person mouad    schedule 23.10.2012