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:
Lightweight execution units based on greenlets.
API that re-uses concepts from the Python standard library (for examples there are events and queues).
Cooperative DNS queries performed through a threadpool, dnspython, or c-ares.
Monkey patching utility to get 3rd party modules to become cooperative
TCP/UDP/HTTP servers
Subprocess support (through gevent.subprocess)
Thread pools
gevent is inspired by eventlet but features a more consistent API, simpler implementation and better performance. Read why others use gevent and check out the list of the open source projects based on gevent.
gevent was written by Denis Bilenko.
Since version 1.1, gevent is maintained by Jason Madden for NextThought with help from the contributors and is licensed under the MIT license.
See what’s new in the latest major release.
Check out the detailed changelog for this version.
The following example shows how to run tasks concurrently.
>>> import gevent
>>> from gevent import socket
>>> urls = ['www.google.com', 'www.example.com', 'www.python.org']
>>> jobs = [gevent.spawn(socket.gethostbyname, url) for url in urls]
>>> _ = gevent.joinall(jobs, timeout=2)
>>> [job.value for job in jobs]
['74.125.79.106', '208.77.188.166', '82.94.164.162']
After the jobs have been spawned, gevent.joinall()
waits for
them to complete, allowing up to 2 seconds. The results are
then collected by checking the value
property.
The gevent.socket.gethostbyname()
function has the same
interface as the standard socket.gethostbyname()
but it does not
block the whole interpreter and thus lets the other greenlets proceed
with their requests unhindered.
The example above used gevent.socket
for socket operations. If the standard socket
module was used the example would have taken 3 times longer to complete because the DNS requests would
be sequential (serialized). Using the standard socket module inside greenlets makes gevent rather
pointless, so what about existing modules and packages that are built
on top of socket
(including the standard library modules like urllib
)?
That’s where monkey patching comes in. The functions in gevent.monkey
carefully
replace functions and classes in the standard socket
module with their cooperative
counterparts. That way even the modules that are unaware of gevent can benefit from running
in a multi-greenlet environment.
>>> from gevent import monkey; monkey.patch_socket()
>>> import requests # it's usable from multiple greenlets now
See Example concurrent_download.py.
Tip
Insight into the monkey-patching process can be obtained by
observing the events gevent.monkey
emits.
Of course, there are several other parts of the standard library that can
block the whole interpreter and result in serialized behavior. gevent
provides cooperative versions of many of those as well. They can be
patched independently through individual functions, but most programs
using monkey patching will want to patch the entire recommended set of
modules using the gevent.monkey.patch_all()
function:
>>> from gevent import monkey; monkey.patch_all()
>>> import subprocess # it's usable from multiple greenlets now
Tip
When monkey patching, it is recommended to do so as early as
possible in the lifetime of the process. If possible,
monkey patching should be the first lines executed. Monkey
patching later, especially if native threads have been
created, atexit
or signal handlers have been installed,
or sockets have been created, may lead to unpredictable
results including unexpected LoopExit
errors.
Instead of blocking and waiting for socket operations to complete (a technique known as polling), gevent arranges for the operating system to deliver an event letting it know when, for example, data has arrived to be read from the socket. Having done that, gevent can move on to running another greenlet, perhaps one that itself now has an event ready for it. This repeated process of registering for events and reacting to them as they arrive is the event loop.
Unlike other network libraries, though in a similar fashion as
eventlet, gevent starts the event loop implicitly in a dedicated
greenlet. There’s no reactor
that you must call a run()
or
dispatch()
function on. When a function from gevent’s API wants to
block, it obtains the gevent.hub.Hub
instance — a special
greenlet that runs the event loop — and switches to it (it is said
that the greenlet yielded control to the Hub). If there’s no
Hub
instance yet, one is automatically created.
Tip
Each operating system thread has its own
Hub
. This makes it possible to use the
gevent blocking API from multiple threads (with care).
The event loop uses the best polling mechanism available on the system by default.
Note
A low-level event loop API is available under the
gevent.core
module. This module is not documented, not meant
for general purpose usage, and its exact contents and semantics
change slightly depending on whether the libev or libuv event loop
is being used. The callbacks supplied to the event loop API are run
in the Hub
greenlet and thus cannot use the
synchronous gevent API. It is possible to use the asynchronous API
there, like gevent.spawn()
and
gevent.event.Event.set()
.
The greenlets all run in the same OS thread and are scheduled
cooperatively. This means that until a particular greenlet gives up
control, (by calling a blocking function that will switch to the
Hub
), other greenlets won’t get a chance to run.
This is typically not an issue for an I/O bound app, but one should be
aware of this when doing something CPU intensive, or when calling
blocking I/O functions that bypass the event loop.
Tip
Even some apparently cooperative functions, like
gevent.sleep()
, can temporarily take priority over
waiting I/O operations in some circumstances.
Synchronizing access to objects shared across the greenlets is
unnecessary in most cases (because yielding control is usually
explicit), thus traditional synchronization devices like the
gevent.lock.BoundedSemaphore
, gevent.lock.RLock
and
gevent.lock.Semaphore
classes, although present, aren’t used very
often. Other abstractions from threading and multiprocessing remain
useful in the cooperative world:
Event
allows one to wake up a number of greenlets
that are calling wait()
method.
AsyncResult
is similar to Event
but
allows passing a value or an exception to the waiters.
Queue
and JoinableQueue
.
New greenlets are spawned by creating a Greenlet
instance
and calling its start
method. (The
gevent.spawn()
function is a shortcut that does exactly that).
The start
method schedules a switch to
the greenlet that will happen as soon as the current greenlet gives up
control. If there is more than one active greenlet, they will be
executed one by one, in an undefined order as they each give up
control to the Hub
.
If there is an error during execution it won’t escape the greenlet’s boundaries. An unhandled error results in a stacktrace being printed, annotated by the failed function’s signature and arguments:
>>> glet = gevent.spawn(lambda : 1/0); glet.join()
>>> gevent.sleep(1)
Traceback (most recent call last):
...
ZeroDivisionError: integer division or modulo by zero
<Greenlet at 0x7f2ec3a4e490: <function <lambda...>> failed with ZeroDivisionError
The traceback is asynchronously printed to sys.stderr
when the greenlet dies.
Greenlet
instances have a number of useful methods:
join
– waits until the greenlet exits;
kill
– interrupts greenlet’s execution;
get
– returns the value returned by greenlet or re-raises the exception that killed it.
Greenlets can be subclassed with care. One use for this is to
customize the string printed after the traceback by subclassing the
Greenlet
class and redefining its __str__
method.
For more information, see Subclassing Greenlet.
Greenlets can be killed synchronously from another greenlet. Killing
will resume the sleeping greenlet, but instead of continuing
execution, a GreenletExit
will be raised.
>>> from gevent import Greenlet
>>> g = Greenlet(gevent.sleep, 4)
>>> g.start()
>>> g.kill()
>>> g.dead
True
The GreenletExit
exception and its subclasses are handled
differently than other exceptions. Raising GreenletExit
is not
considered an exceptional situation, so the traceback is not printed.
The GreenletExit
is returned by get
as if it were returned by the greenlet, not
raised.
The kill
method can accept a custom exception to be raised:
>>> g = Greenlet.spawn(gevent.sleep, 5) # spawn() creates a Greenlet and starts it
>>> g.kill(Exception("A time to kill"))
Traceback (most recent call last):
...
Exception: A time to kill
Greenlet(5) failed with Exception
The kill
can also accept a timeout
argument specifying the number of seconds to wait for the greenlet to
exit. Note that kill
cannot guarantee
that the target greenlet will not ignore the exception (i.e., it might
catch it), thus it’s a good idea always to pass a timeout to
kill
(otherwise, the greenlet doing the
killing will remain blocked forever).
Tip
The exact timing at which an exception is raised within a target
greenlet as the result of kill
is
not defined. See that function’s documentation for more details.
Caution
Use care when killing greenlets, especially arbitrary
greenlets spawned by a library or otherwise executing code you are
not familiar with. If the code being executed is not prepared to
deal with exceptions, object state may be corrupted. For example,
if it has acquired a Lock
but does not use a finally
block to release it, killing the greenlet at the wrong time could
result in the lock being permanently locked:
def func():
# DON'T DO THIS
lock.acquire()
socket.sendall(data) # This could raise many exceptions, including GreenletExit
lock.release()
This document describes a similar situation for threads.
Greenlets also function as context managers, so you can combine spawning and waiting for a greenlet to finish in a single line:
>>> def in_greenlet():
... print("In the greenlet")
... return 42
>>> with Greenlet.spawn(in_greenlet) as g:
... print("In the with suite")
In the with suite
In the greenlet
>>> g.get(block=False)
42
Many functions in the gevent API are synchronous, blocking the current
greenlet until the operation is done. For example, kill
waits until the target greenlet is
dead
before returning [1]. Many of
those functions can be made asynchronous by passing the keyword argument
block=False
.
Furthermore, many of the synchronous functions accept a timeout
argument, which specifies a limit on how long the function can block
(examples include gevent.event.Event.wait()
,
gevent.Greenlet.join()
, gevent.Greenlet.kill()
,
gevent.event.AsyncResult.get()
, and many more).
The socket
and SSLObject
instances can also have a timeout, set by the
settimeout
method.
When these are not enough, the gevent.Timeout
class and
gevent.with_timeout()
can be used to add timeouts to arbitrary
sections of (cooperative, yielding) code.
To limit concurrency, use the gevent.pool.Pool
class (see
Example dns_mass_resolve.py).
Gevent comes with TCP/SSL/HTTP/WSGI servers. See Implementing servers.
There are a number of configuration options for gevent. See Configuring gevent for details. This document also explains how to enable gevent’s builtin monitoring and debugging features.
The objects in gevent.util
may be helpful for monitoring and
debugging purposes.
See API reference for a complete API reference.
Gevent for working Python developer is a comprehensive tutorial.
Footnotes
Next page: gevent
– common functions