为何Greenlet数量会影响HTTP请求的响应耗时?
Hey Roger, let's break down why you're seeing slower response times as concurrency ramps up with gevent, and walk through fixes to get better performance:
The requests library uses a default HTTPAdapter with a max connection pool size of 10 (pool_connections=10, pool_maxsize=10). That means even if you spawn 100 gevent coroutines, only 10 can actually establish a connection to your target server at once. The other 90 will sit idle waiting for connections to free up, which directly inflates your response times.
Fix it by expanding the connection pool:
import gevent from gevent import monkey import requests from requests.adapters import HTTPAdapter # Patch first - critical to do this before importing other network-related modules monkey.patch_all(thread=False) # Create a session with an expanded connection pool session = requests.Session() # Match your concurrency level with pool size (adjust based on testing) adapter = HTTPAdapter(pool_connections=100, pool_maxsize=100) session.mount("https://", adapter) session.mount("http://", adapter) def action(): resp = session.get("https://www.google.com") if resp.status_code == 200: print(resp.elapsed.total_seconds()) jobs = [gevent.spawn(action) for _ in range(100)] gevent.joinall(jobs)
When you hit a server with 100 concurrent requests, it’s likely to trigger rate limiting, queueing, or resource exhaustion on the server side. Services like Google actively manage request load from single IPs, so your requests might be delayed intentionally to prevent abuse.
Mitigate this with controlled concurrency:
- Use
gevent.Poolto cap concurrent requests at a server-friendly level (test to find the sweet spot):
with gevent.Pool(30) as pool: # Map the action function to 100 tasks, capped at 30 concurrent pool.map(lambda _: action(), range(100))
- Add gentle retry logic for timeouts or 5xx errors (avoid aggressive retries that worsen the problem):
from requests.exceptions import RequestException import time def action(): retries = 3 for attempt in range(retries): try: resp = session.get("https://www.google.com", timeout=5) if resp.status_code == 200: print(resp.elapsed.total_seconds()) break except RequestException: if attempt < retries - 1: time.sleep(0.5) else: print("Request failed after retries")
You’re already patching before importing requests (good!), but double-check:
monkey.patch_all(thread=False)disables thread-related patches, which is fine for requests since it uses sockets, but ensure no network-related modules are imported before patching. If any module initializes sockets pre-patch, coroutine switching won’t work as expected, leading to blocking waits.- For DNS bottlenecks (common in high concurrency), enable asynchronous DNS patching:
monkey.patch_all(thread=False, dns=True)
If you really want to squeeze out performance, consider switching to httpx—it supports HTTP/2 multiplexing (which lets multiple requests share a single connection) and plays nicely with gevent. Here’s a quick example:
import httpx from gevent import monkey, spawn, joinall monkey.patch_all(thread=False) client = httpx.Client(http2=True) def action(): resp = client.get("https://www.google.com") if resp.status_code == 200: print(resp.elapsed.total_seconds()) jobs = [spawn(action) for _ in range(100)] joinall(jobs)
内容的提问来源于stack exchange,提问作者Roger

