mrkeyoor.com_
Thu 06 Aug 15:41 UTC
PyPIWeb Backendupdated 06 Aug 2026

gevent

gevent makes ordinary blocking Python code run concurrently without async and await. It runs your functions inside greenlets, which are lightweight coroutines from the greenlet library, and drives them with a libev or libuv event loop. The trick is monkey patching: gevent.monkey.patch_all() swaps the standard library's socket, ssl, select, threading, time and subprocess modules for cooperative versions, so a plain requests.get or a psycopg2 query yields to the event loop while it waits on the network instead of blocking the process. Code you already wrote keeps its shape; a call that used to hold up one worker now lets thousands of other greenlets run. It also ships stdlib-shaped concurrency tools of its own: gevent.pool, gevent.queue, gevent.event, gevent.lock and a WSGI server.

Verdict

Still the most practical way to get a large blocking Python codebase serving thousands of connections without rewriting it, and the gunicorn gevent worker has kept plenty of services alive that would otherwise need a port to asyncio. For new code the implicit switching, the C-extension audits and the lack of a free-threading story make it a hard sell against asyncio.

API stability5/5The spawn, joinall, Pool, Queue and monkey.patch_all surface has been stable for a decade, and the only deprecation queued in 26.7.0 is that patch_all will require keyword arguments in early 2027; what does move is the supported Python floor, with 3.9 removed in 26.4.0
Docs4/5gevent.org has a full API reference, an introduction covering the greenlet model, dedicated pages on loop implementations, monitoring, DNS resolvers and servers, and a towncrier changelog that explains the actual bug behind each fix; the weak spot is guidance on which third-party C extensions cooperate, which is the question most newcomers arrive with
Maintenance4/5Pushed 2026-08-03 with releases in April, May and July 2026, wheels already built against Python 3.15 betas and Windows ARM, maintained by one person on behalf of a sponsoring company since 1.1; there are 131 open issues (136 including PRs) and the PyPI classifier still reads Development Status :: 4 - Beta
Ecosystem4/5About 12.2M downloads a week, a first-class gunicorn worker class, and long-standing use in Gunicorn, Locust, Sentry and similar tools; the surrounding ecosystem is shrinking though, since new libraries target asyncio and the green-thread world is down to gevent and eventlet

Use it if

  • You have an existing synchronous codebase built on requests, psycopg2 or a Django or Flask app and need thousands of concurrent connections without rewriting every function as a coroutine
  • You deploy WSGI behind gunicorn, where the gevent worker class is the standard way to serve an I/O-bound app at high concurrency with a small number of processes
  • Your bottleneck is waiting on sockets rather than burning CPU, for example an API gateway, a crawler, or a service that fans out to a dozen internal endpoints per request
  • You want concurrency primitives that look like the ones you already know, since gevent.queue.Queue, gevent.event.Event and gevent.lock.Semaphore mirror the queue and threading APIs closely enough to port code mechanically
  • You need per-greenlet timeouts and cancellation, which gevent.Timeout applies to any blocking call in the block without the callee knowing anything about it
Skip it if

Setup reality

pip install gevent is usually a wheel download: 26.7.0 ships 45 of them covering CPython 3.10 through 3.15 on manylinux_2_28, musllinux, macOS and Windows including ARM64. It pulls greenlet, zope.event and zope.interface, plus cffi on Windows. On Python 3.9 the metadata claims support but no wheel exists, so pip falls back to compiling libev from source and you need a C toolchain. The install is the easy part. Monkey patching has to happen at the very top of your entry module, before anything imports socket, ssl or threading, or you get a MonkeyPatchWarning at best and half-patched modules that block the loop at worst; python -m gevent.monkey yourscript.py does the patching from outside when you cannot control import order. After that, audit every native dependency for blocking calls, install psycogreen if you use psycopg2, and be aware that GEVENT_LOOP picks between libev and libuv, with the Cython libev backend as the default on CPython outside Windows.

Patterns

Patch the standard library before anything elsemonkey-patch-first

# main.py: these two lines must come before every other import
from gevent import monkey
monkey.patch_all()

import requests
import gevent

urls = ['https://example.com', 'https://example.org', 'https://example.net']
jobs = [gevent.spawn(requests.get, u) for u in urls]
gevent.joinall(jobs, timeout=10)
print([j.value.status_code for j in jobs if j.value])

If any module imported ssl, socket or threading before patch_all runs, gevent prints a MonkeyPatchWarning and those references stay blocking. When you cannot control import order, for example under a framework runner, use python -m gevent.monkey yourscript.py to patch from outside.

Run work concurrently and gather resultsspawn-and-collect

import gevent

def fetch(item):
    return expensive_io(item)

jobs = [gevent.spawn(fetch, i) for i in items]
gevent.joinall(jobs)

results = []
for job in jobs:
    if job.successful():
        results.append(job.value)
    else:
        log.warning('failed: %r', job.exception)

An exception inside a greenlet does not propagate to the spawner; it is stored on job.exception and the default hub prints it to stderr. Checking successful() on every job is the difference between handling failures and silently dropping them.

Cap how many greenlets run at oncebound-concurrency

from gevent.pool import Pool

pool = Pool(50)
for url in one_million_urls():
    pool.spawn(fetch, url)
pool.join()

# map-style, returns results in order
results = Pool(50).map(fetch, urls)

Greenlets are cheap but file descriptors and the remote server are not. Pool.spawn blocks once the pool is full, which applies backpressure to your producer loop; an unbounded gevent.spawn over a million URLs will exhaust descriptors instead.

Put a deadline on any blocking codetimeouts

import gevent
from gevent import Timeout

with Timeout(5, False) as t:
    data = legacy_client.read_everything()  # knows nothing about gevent
    # on expiry the block exits and data is unbound

# raise instead of returning quietly
try:
    with Timeout(5):
        data = legacy_client.read_everything()
except Timeout:
    data = None

# one-shot form
result = gevent.with_timeout(5, legacy_client.read_everything, timeout_value=None)

Timeout works by raising inside the greenlet at its next switch, so it only fires on code that yields. A tight CPU loop with no I/O will run past the deadline untouched, which is the same reason one busy greenlet stalls the whole hub.

Hand work between greenlets with a queueproducer-consumer

import gevent
from gevent.queue import Queue, JoinableQueue

q = JoinableQueue(maxsize=100)

def worker():
    while True:
        item = q.get()
        try:
            handle(item)
        finally:
            q.task_done()

workers = [gevent.spawn(worker) for _ in range(10)]
for item in source():
    q.put(item)
q.join()
gevent.killall(workers)

gevent.queue mirrors the stdlib queue API but blocks cooperatively instead of holding the thread. maxsize is what gives you backpressure; without it a fast producer builds an unbounded in-memory list. Remember to kill the workers, since they loop forever.

Wait for a result or a one-shot eventsignal-between-greenlets

from gevent.event import Event, AsyncResult

ready = Event()

def waiter():
    ready.wait(timeout=30)
    print('go')

result = AsyncResult()

def producer():
    try:
        result.set(compute())
    except Exception as exc:
        result.set_exception(exc)

value = result.get(timeout=10)  # re-raises whatever set_exception got

Event is a latch for many waiters with no payload; AsyncResult carries a single value or exception to whoever calls get(). AsyncResult.get re-raises in the calling greenlet, which is the cleanest way to move an error across a greenlet boundary.

Serve a WSGI app with the gevent workergunicorn-worker

# gunicorn.conf.py
worker_class = 'gevent'
workers = 4                  # about one per CPU core
worker_connections = 1000    # greenlets per worker

def post_fork(server, worker):
    from psycogreen.gevent import patch_psycopg
    patch_psycopg()

# gunicorn -c gunicorn.conf.py app:application

gunicorn monkey patches for you inside the gevent worker, so do not call patch_all yourself in the app module. Keep workers near the core count and scale concurrency with worker_connections; the psycopg2 patch has to run after fork, in each worker.

Run gevent's own HTTP serverstandalone-wsgi-server

from gevent import monkey; monkey.patch_all()

from gevent.pywsgi import WSGIServer
from gevent.pool import Pool

def app(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    return [b'ok']

server = WSGIServer(('0.0.0.0', 8080), app, spawn=Pool(1000))
server.serve_forever()

Passing spawn=Pool(n) caps concurrent requests; the default spawns an unbounded greenlet per connection, which a slowloris-style client will happily exploit. pywsgi is fine behind a reverse proxy but has no process management, so most deployments still put gunicorn in front.

Push uncooperative C code onto real threadsoffload-blocking-calls

import gevent
from gevent.threadpool import ThreadPool

cpu_pool = ThreadPool(4)

def handler():
    # a C extension that ignores the patched socket module
    result = cpu_pool.apply(legacy_native_call, (payload,))
    return result

# or use the hub's shared pool
result = gevent.get_hub().threadpool.apply(legacy_native_call, (payload,))

This is the escape hatch for drivers and libraries that never became cooperative. The work still contends for the GIL, so it only helps for calls that release it, which most well-behaved C extensions do around I/O. Size the pool deliberately rather than sharing the hub's.

Make psycopg2 yield instead of blockingcooperative-postgres

from gevent import monkey; monkey.patch_all()

from psycogreen.gevent import patch_psycopg
patch_psycopg()

import psycopg2
conn = psycopg2.connect(dsn)  # now yields while waiting on the server

Without psycogreen, every query blocks the entire hub because psycopg2 talks to libpq in C and never touches the patched socket module. Call patch_psycopg after patch_all and, under gunicorn, inside post_fork. psycopg 3 in async mode and asyncpg both sit outside this world entirely.

Find the greenlet that is stalling everythingdetect-blocked-loop

import gevent

gevent.config.monitor_thread = True
gevent.config.max_blocking_time = 0.5  # seconds

# or from the environment, before the process starts:
# GEVENT_MONITOR_THREAD_ENABLE=true GEVENT_MAX_BLOCKING_TIME=0.5 python main.py

from gevent.util import print_run_info
print_run_info()  # tree of live greenlets and their stacks

The monitor runs on a real OS thread, so it still reports when the hub is wedged, printing a run-info dump for any greenlet that holds the loop past max_blocking_time. Set the config immediately after importing gevent, before patching. Memory monitoring needs psutil, which comes with the monitor extra.

Shell out without blocking the loopcooperative-subprocess

from gevent import monkey; monkey.patch_all()

import subprocess
from gevent.subprocess import Popen, PIPE

p = Popen(['ffmpeg', '-i', src, dst], stdout=PIPE, stderr=PIPE)
out, err = p.communicate(timeout=60)
print(p.returncode)

patch_all replaces subprocess with gevent.subprocess, so plain subprocess.run also becomes cooperative. Reading a child's output with the unpatched module blocks every greenlet until the process exits, which is a common surprise in code that shells out inside a request handler.

Alternatives

PackageRegistryPick it when
eventletPyPIYou need the same green-thread and monkey-patching model but are already invested in eventlet's API or an OpenStack-era codebase
anyioPyPIYou are willing to write explicit async and await and want structured concurrency that runs on both asyncio and trio
uvloopPyPIYou already use asyncio and just want a faster libuv-backed event loop under it, with no patching involved
trioPyPIYou want explicit cancellation scopes and nurseries so that concurrency and failure paths are visible in the source