Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is `gevent.spawn` different than a monkeypatched `threading.Thread()`?

While double checking that threading.Condition is correctly monkey patched, I noticed that a monkeypatched threading.Thread(…).start() behaves differently from gevent.spawn(…).

Consider:

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

Note, specifically, the two comments starting with XXX.

When using the first line (with gevent.spawn), the first thread.join() raises an exception:

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

However, Thread(…).start() (the second block), everything works as expected.

Why would this be? What's the difference between gevent.spawn() and Thread(…).start()?

like image 235
David Wolever Avatar asked Oct 23 '12 22:10

David Wolever


People also ask

What is gevent spawn?

gevent is a coroutine -based Python networking library that uses greenlet to provide a high-level synchronous API on top of the libev or libuv event loop. Features include: Fast event loop based on libev or libuv. Lightweight execution units based on greenlets.

What is gevent pool?

An equivalent of itertools. imap() , operating in parallel. The func is applied to each element yielded from each iterable in iterables in turn, collecting the result. If this object has a bound on the number of active greenlets it can contain (such as Pool ), then at most that number of tasks will operate in parallel.

What is gevent monkey patch?

monkey – Make the standard library cooperative. Make the standard library cooperative. The primary purpose of this module is to carefully patch, in place, portions of the standard library with gevent-friendly functions that behave in the same way as the original (at least as closely as possible).

Is gevent async?

In gevent, you don't use an "async" or "await" syntax. You just spawn greenlets (green threads), which run in the background, and if you want, you can have them block until they return a result. And they're efficient, so you can spawn thousands without a problem.


1 Answers

What happen in your code is that the greenlets that you have created in you threads list didn't have yet the chance to be executed because gevent will not trigger a context switch until you do so explicitly in your code using gevent.sleep() and such or implicitly by calling a function that block e.g. semaphore.wait() or by yielding and so on ..., to see that you can insert a print before cv.wait() and see that it's called only after cv.notify_all() is called:

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

So an easy fix to your code will be to insert something that will trigger a context switch after you create your list of greenlets, example:

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

Note: I am still new to gevent so i don't know if this is the right way to do it :)

This way all the greenlets will have the chance to be executed and each one of them will trigger a context switch when they call cv.wait() and in the mean time they will register them self to the condition waiters so that when cv.notify_all() is called it will notify all the greenlets.

HTH,

like image 174
mouad Avatar answered Sep 20 '22 04:09

mouad