2012-10-23 15 views
8

Podczas podwójnego sprawdzania, czy threading.Condition jest poprawnie załatany małpami, zauważyłem, że monkeypatched threading.Thread(…).start() zachowuje się inaczej niż gevent.spawn(…).Dlaczego "gevent.spawn" różni się od monkeypatched `threading.Thread()`?

Rozważmy:

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

Uwaga, konkretnie, dwa komentarze zaczynające się XXX.

Podczas korzystania z pierwszej linii (z gevent.spawn), pierwszy thread.join() podnosi wyjątek:

 
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 

Jednak Thread(…).start() (drugi blok), wszystko działa zgodnie z oczekiwaniami.

Dlaczego miałoby to być? Jaka jest różnica między gevent.spawn() i Thread(…).start()?

Odpowiedz

5

Co wydarzy się w kodzie jest to, że greenlets, które zostały utworzone w ciebie threads liście nie ma jeszcze szansę zostać wykonana, ponieważ gevent nie spowoduje przełączenie kontekstu, aż to zrobić jawnie w kodzie za pomocą gevent.sleep() i takie lub niejawne przez wywołanie funkcji, która blokuje np semaphore.wait() lub plonowanie i tak dalej ..., aby zobaczyć, że można wstawić wydruk przed cv.wait() i widzę, że to się nazywa dopiero po cv.notify_all() nazywa się:

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

Więc łatwo naprawić w kodzie będzie wstawić coś, co spowoduje przełączenie kontekstu po utworzeniu listy greenlets, przykład:

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

Uwaga: wciąż jestem nowy na gevent więc nie wiem, czy jest to dobry sposób, żeby zrobić to :)

ten sposób wszystkie greenlets będzie miał szansę zostać zrealizowane i każdy z nich będzie wyzwalać przełączenie kontekstu, gdy nazywają cv.wait() iw tym czasie będą zarejestrować im siebie kelnerów stan tak, że kiedy cv.notify_all() nazywa się to powiadomi wszystkie greenlety.

HTH,

Powiązane problemy