01-5. Gevent Worker 詳解
⏱️ 閱讀時間: 10 分鐘 🎯 難度: ⭐⭐⭐ (中等)
⚠️ 重要提醒:技術定位
Gevent 是一個成熟但相對過時的技術,主要用於維護舊專案或 WSGI 應用的過渡階段。
現代 Python 異步方案
新專案應該優先考慮:
- ASGI + Uvicorn/Daphne:Python 官方的異步標準
- Django 3.0+ async views:Django 原生 async/await 支持
- FastAPI:現代異步 Web 框架
# 現代方案(推薦)
pip install uvicorn
uvicorn myproject.asgi:application --workers 4
# Gevent 方案(舊專案維護)
pip install gunicorn gevent
gunicorn myproject.wsgi:application --worker-class gevent何時使用 Gevent?
✅ 適合使用:
- 維護舊的 WSGI Django 專案
- 暫時無法遷移到 ASGI 的專案
- 需要快速提升舊應用的並發能力
❌ 不建議使用:
- 新專案:直接用 ASGI + asyncio
- Django 3.0+ 專案:使用原生 async views
- 需要長期維護的項目:避免技術債務
🎯 本篇重點
理解 Gevent Worker(協程 Worker)如何用一個進程處理成千上萬個並發請求,以及為什麼它逐漸被 asyncio 取代。
🤔 什麼是 Gevent Worker?
一句話解釋
Gevent Worker = 使用協程(Coroutine)的 Worker,一個進程可以同時處理數千個請求。
🎪 用雜技演員來比喻
Sync Worker = 普通演員
演員 A:
1. 拋球 1 → 等球落下 → 接住 → 完成 ✅
2. 拋球 2 → 等球落下 → 接住 → 完成 ✅
3. 拋球 3 → 等球落下 → 接住 → 完成 ✅
一次只能處理一個球Gevent Worker = 雜耍演員
演員 B(雜耍大師):
1. 拋球 1 ↗️(球在空中飛)
2. 趁球 1 還在空中,拋球 2 ↗️
3. 趁球 1、2 還在空中,拋球 3 ↗️
4. 接住球 1 ✅
5. 拋球 4 ↗️
6. 接住球 2 ✅
...
同時處理多個球!關鍵: 利用「等待時間」做其他事情!
💻 Gevent Worker 的工作原理
啟動 Gevent Worker
# 1. 先安裝 gevent
pip install gevent
# 2. 啟動 Gunicorn with Gevent
gunicorn myproject.wsgi:application \
--workers 4 \
--worker-class gevent \
--worker-connections 1000 \
--bind 0.0.0.0:8000重點參數:
--worker-class gevent:使用協程模式--worker-connections 1000:每個 Worker 最多 1000 個並發連接
總並發數 = workers × worker-connections = 4 × 1000 = 4000
協程的運作方式
# Gevent Worker 的內部邏輯(簡化版)
class GeventWorker:
def __init__(self):
self.greenlets = [] # 協程列表
def run(self):
while True:
# 1. 接收新請求,創建協程
request = self.accept_request()
greenlet = gevent.spawn(self.handle_request, request)
self.greenlets.append(greenlet)
# 2. 在所有協程間切換
# 當一個協程在等待 I/O 時,自動切換到其他協程
gevent.joinall(self.greenlets, timeout=0.01)
def handle_request(self, request):
# 處理單個請求
response = self.process(request)
self.send_response(response)核心概念:
- Greenlet(綠色線程):超輕量的協程
- 自動切換:等待 I/O 時自動切換到其他協程
- 非阻塞:不會傻傻等待
📊 實際運作範例
場景:處理 3 個外部 API 請求
# views.py
import requests
def fetch_api(request):
# 呼叫外部 API(需要 2 秒)
response = requests.get('https://api.example.com/data')
return JsonResponse(response.json())Sync Worker(1 個 Worker)
時間軸:
0秒 → 請求 A 到達,開始呼叫 API
等待 API 回應... ⏸️(Worker 閒置)
請求 B 到達,等待中... ⏳
請求 C 到達,等待中... ⏳
2秒 → 請求 A 完成 ✅
開始處理請求 B
等待 API 回應... ⏸️
4秒 → 請求 B 完成 ✅
開始處理請求 C
等待 API 回應... ⏸️
6秒 → 請求 C 完成 ✅
總時間:6 秒
CPU 利用率:幾乎 0%(都在等待)Gevent Worker(1 個 Worker)
時間軸:
0秒 → 請求 A 到達,發送 API 請求
↓(不等待!立即切換)
請求 B 到達,發送 API 請求
↓(不等待!立即切換)
請求 C 到達,發送 API 請求
↓
現在有 3 個 API 請求在飛行中...
2秒 → 請求 A 的 API 回應了 ✅
請求 B 的 API 回應了 ✅
請求 C 的 API 回應了 ✅
總時間:2 秒
並發處理:3 個請求同時進行!效能提升:3 倍!
🔍 Gevent 的核心技術
1. Monkey Patching(猴子補丁)
Gevent 需要 “Monkey Patch” 才能正常工作:
# gunicorn_config.py
import gevent.monkey
gevent.monkey.patch_all()
# 這會替換 Python 的標準庫:
# - socket → gevent.socket(非阻塞)
# - time.sleep → gevent.sleep(非阻塞)
# - threading → gevent(協程)為什麼需要?
# 沒有 Monkey Patch
import time
time.sleep(2) # 阻塞整個 Worker ❌
# 有 Monkey Patch
import time
time.sleep(2) # 自動切換到其他協程 ✅2. 事件循環(Event Loop)
# Gevent 的事件循環(簡化概念)
event_loop = EventLoop()
# 協程 1:處理請求 A
def handle_request_a():
print("請求 A:發送 API 請求")
response = requests.get('https://api.com') # 等待 I/O
# ↑ 在這裡自動切換到協程 2!
print("請求 A:收到回應")
# 協程 2:處理請求 B
def handle_request_b():
print("請求 B:發送 API 請求")
response = requests.get('https://api.com') # 等待 I/O
# ↑ 在這裡自動切換到協程 3!
print("請求 B:收到回應")
# 事件循環管理所有協程
event_loop.add(handle_request_a)
event_loop.add(handle_request_b)
event_loop.run()輸出:
請求 A:發送 API 請求
請求 B:發送 API 請求 ← 立即切換!
請求 A:收到回應 ← 哪個先回應就先處理
請求 B:收到回應3. Greenlet(輕量級協程)
# Greenlet 的特性
# 記憶體占用對比:
Thread: 8 MB ← 線程
Greenlet: 4 KB ← 協程(輕量 2000 倍!)
# 創建速度對比:
Thread: 1 ms ← 線程
Greenlet: 0.001 ms ← 協程(快 1000 倍!)
# 因此可以創建大量協程:
1 個 Gevent Worker = 1000-10000 個協程 ✅⚖️ Gevent Worker 的優缺點
✅ 優點
| 優點 | 說明 | 數據 |
|---|---|---|
| 高並發 | 單個 Worker 可處理上千連接 | 1 worker = 1000+ 連接 |
| 記憶體效率 | 協程比線程輕量 2000 倍 | 1 greenlet = 4 KB |
| I/O 友好 | 自動利用等待時間 | CPU 利用率 > 80% |
| 程式碼簡單 | 不需要 async/await 語法 | 用同步的方式寫異步代碼 |
❌ 缺點
| 缺點 | 說明 | 影響 |
|---|---|---|
| 需要 Monkey Patch | 可能影響某些第三方庫 | 兼容性問題 |
| 不適合 CPU 密集 | 協程無法利用多核 | 計算任務慢 |
| 除錯困難 | 協程切換不可見 | 錯誤難追蹤 |
| C 擴展可能阻塞 | 某些 C 庫不支援協程 | 仍會阻塞 |
🎯 Gevent Worker 的適用場景
✅ 非常適合
1. 大量外部 API 呼叫
# 範例:聚合多個數據源
def dashboard(request):
# 需要呼叫 5 個外部 API
# ❌ Sync Worker:串行執行(10 秒)
weather = requests.get('https://api.weather.com') # 2秒
news = requests.get('https://api.news.com') # 2秒
stocks = requests.get('https://api.stocks.com') # 2秒
social = requests.get('https://api.twitter.com') # 2秒
analytics = requests.get('https://api.analytics.com') # 2秒
# ✅ Gevent Worker:並發執行(2 秒)
# 所有請求幾乎同時發出,等待時間重疊!
return JsonResponse({...})效能提升:5 倍!
2. 高並發 Web 應用
# 範例:社交媒體 API
def get_feed(request):
user = request.user
# 查詢資料庫(I/O)
posts = Post.objects.filter(
author__in=user.following.all()
)[:50]
# 對每篇貼文獲取讚數(Redis)
for post in posts:
post.likes = cache.get(f'post:{post.id}:likes')
return JsonResponse(...)
# 同時 5000 個用戶訪問:
# - Sync Worker(4 workers):只能同時處理 4 個 ❌
# - Gevent Worker(4 workers, 1000 connections):可處理 4000 個 ✅3. 爬蟲或數據抓取
# 範例:批量抓取網頁
def scrape_websites(request):
urls = [
'https://example1.com',
'https://example2.com',
# ... 100 個 URLs
]
# ❌ Sync Worker:串行抓取(100 秒)
results = []
for url in urls:
response = requests.get(url) # 每個 1 秒
results.append(response.text)
# ✅ Gevent Worker:並發抓取(2-3 秒)
# 使用 gevent 的協程池
import gevent
from gevent import monkey
monkey.patch_all()
jobs = [gevent.spawn(requests.get, url) for url in urls]
gevent.joinall(jobs)
results = [job.value.text for job in jobs]
return JsonResponse({'count': len(results)})效能提升:30-50 倍!
4. WebSocket 或長連接
# 範例:即時通知系統
def notification_stream(request):
def event_stream():
while True:
# 檢查新通知(Redis)
notification = get_new_notification(request.user)
if notification:
yield f"data: {notification}\n\n"
gevent.sleep(1) # 等待 1 秒(不阻塞其他連接)
return StreamingHttpResponse(event_stream())
# 1000 個用戶同時連接:
# - Sync Worker:需要 1000 個 workers ❌(不可能)
# - Gevent Worker:1 個 worker 就夠 ✅❌ 不適合
1. CPU 密集型任務
# ❌ 不適合:複雜計算
def calculate_primes(request):
n = 1000000
primes = []
for num in range(2, n):
is_prime = True
for i in range(2, int(num ** 0.5) + 1):
if num % i == 0:
is_prime = False
break
if is_prime:
primes.append(num)
return JsonResponse({'count': len(primes)})
# 問題:
# - 沒有 I/O 等待,協程無法切換
# - 一個計算密集的請求會阻塞所有其他請求
# - 無法利用多核 CPU解決方案: 使用 Sync Worker 或 多進程
2. 不兼容 Monkey Patch 的庫
# ❌ 某些 C 擴展庫可能不支援
import psycopg2 # PostgreSQL 驅動
# 可能導致問題:
# - 資料庫連接阻塞
# - 協程無法正常切換
# ✅ 解決方案:使用支援協程的庫
import psycopg2.pool # 使用連接池
# 或
from psycogreen.gevent import patch_psycopg # 打補丁
patch_psycopg()🔧 Gevent Worker 最佳實踐
1. 完整配置
# gunicorn_config.py
import gevent.monkey
gevent.monkey.patch_all()
import multiprocessing
# Worker 配置
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = 'gevent'
worker_connections = 1000
# 超時配置
timeout = 60 # Gevent 可以處理更長的連接
# 連接配置
keepalive = 5
# 日誌
loglevel = 'info'
accesslog = '-'
errorlog = '-'2. 使用協程池
# 批量處理時使用協程池
from gevent.pool import Pool
def batch_process(request):
urls = request.POST.getlist('urls')
# 創建協程池(最多同時 50 個)
pool = Pool(50)
# 並發處理
results = pool.map(fetch_url, urls)
return JsonResponse({'results': results})
def fetch_url(url):
response = requests.get(url)
return response.json()優點:
- 限制並發數,避免過載
- 自動管理協程生命週期
3. 錯誤處理
import gevent
from gevent import Timeout
def fetch_with_timeout(request):
url = request.GET.get('url')
# 設置超時(5 秒)
timeout = Timeout(5)
timeout.start()
try:
response = requests.get(url)
return JsonResponse(response.json())
except Timeout:
return JsonResponse({'error': 'Request timeout'}, status=408)
except Exception as e:
return JsonResponse({'error': str(e)}, status=500)
finally:
timeout.close()4. 監控協程數量
# 查看當前協程數量
import gevent
def status(request):
greenlets = len(gevent._greenlet.greenlets)
return JsonResponse({
'active_greenlets': greenlets,
'max_connections': 1000
})📊 效能對比實測
測試場景:呼叫外部 API
# views.py
import requests
import time
def api_call(request):
# 模擬外部 API(1 秒延遲)
response = requests.get('http://httpbin.org/delay/1')
return JsonResponse(response.json())壓力測試
# 測試:1000 個請求,100 並發
# Sync Worker(4 workers)
ab -n 1000 -c 100 http://localhost:8000/api/
結果:
- Requests per second: 4 [#/sec] ← 一次只能處理 4 個
- Time taken: 250 秒
# Gevent Worker(4 workers, 1000 connections)
ab -n 1000 -c 100 http://localhost:8000/api/
結果:
- Requests per second: 95 [#/sec] ← 幾乎全部並發
- Time taken: 10.5 秒
效能提升:23.8 倍!🚀🎤 面試常見問題
Q1: Gevent 如何實現高並發?
答案:
Gevent 使用**協程(Coroutine/Greenlet)**實現高並發:
- 輕量級:每個協程只佔用 4KB 記憶體(線程需要 8MB)
- 自動切換:當協程等待 I/O 時,自動切換到其他協程
- 非阻塞:利用等待時間處理其他請求
- 單進程:在一個進程內通過事件循環管理上千協程
一個 Gevent Worker 可以同時處理 1000+ 個連接,而 Sync Worker 只能處理 1 個。
Q2: 什麼是 Monkey Patching?為什麼需要?
答案:
Monkey Patching 是指在運行時動態替換 Python 標準庫的功能。
為什麼需要:
# 標準的 socket 是阻塞的 import socket s = socket.socket() s.connect(...) # 阻塞! # Gevent 需要替換成非阻塞版本 import gevent.monkey gevent.monkey.patch_all() # 現在 socket 變成 gevent.socket(非阻塞)這樣第三方庫(如 requests)無需修改就能支援協程。
Q3: Gevent Worker 適合什麼場景?
答案:
Gevent Worker 適合 I/O 密集型場景:
適合:
- 大量外部 API 呼叫
- 高並發 Web 應用
- 爬蟲和數據抓取
- WebSocket、長連接
- 微服務間的 RPC 呼叫
不適合:
- CPU 密集型計算(無法利用多核)
- 需要原生線程的場景
- 不兼容 Monkey Patch 的庫
Q4: Gevent vs Sync Worker,如何選擇?
答案:
使用 Gevent Worker 當:
- 應用主要是等待(API、資料庫、網絡)
- 需要處理大量並發連接
- 每個請求處理時間較長(> 500ms)
使用 Sync Worker 當:
- 應用主要是計算(數據分析、圖像處理)
- 每個請求很快(< 100ms)
- 並發需求不高(< 100 連接)
快速判斷:
if 請求中有大量等待時間: use_gevent() elif 請求中主要是計算: use_sync()
🆚 Gevent vs 現代 Asyncio
技術對比
| 特性 | Gevent (舊) | ASGI + Asyncio (新) |
|---|---|---|
| 標準支持 | ❌ 第三方庫 | ✅ Python 官方標準 |
| 語法 | 同步風格 | async/await 明確 |
| Monkey Patch | ⚠️ 需要,有兼容性風險 | ✅ 不需要 |
| 生態系統 | ⚠️ 逐漸減少 | ✅ 積極發展 |
| Django 支持 | WSGI only | Django 3.0+ 原生支持 |
| 除錯 | ⭐⭐ 困難 | ⭐⭐⭐⭐ 較容易 |
| 學習曲線 | 低(看起來像同步) | 高(需要理解 async) |
| 長期維護 | ⚠️ 技術債務 | ✅ 推薦 |
代碼對比
# Gevent 方式(舊)
import gevent.monkey
gevent.monkey.patch_all()
import requests
def fetch_data(request):
# 看起來是同步,實際是異步
response = requests.get('https://api.example.com')
return JsonResponse(response.json())
# ASGI + Asyncio 方式(新)
import httpx
async def fetch_data(request):
# 明確的 async/await 語法
async with httpx.AsyncClient() as client:
response = await client.get('https://api.example.com')
return JsonResponse(response.json())遷移建議
如果你正在使用 Gevent:
- 短期:繼續使用沒問題,但要了解它的限制
- 中期:計劃遷移到 ASGI(Django 3.0+ 支持)
- 長期:新功能用 async views,舊代碼逐步重構
如果你要開新專案:
# 直接使用現代方案
django-admin startproject myproject
pip install uvicorn
# 運行 ASGI 服務器
uvicorn myproject.asgi:application --workers 4✅ 重點回顧
Gevent Worker 的核心
- 使用協程(Greenlet)實現高並發
- 一個 Worker = 1000+ 並發連接
- 需要 Monkey Patch 改造標準庫
- 自動在 I/O 等待時切換協程
技術定位
- ⚠️ 舊專案維護:適合快速提升 WSGI 應用的並發能力
- ❌ 新專案開發:應使用 ASGI + asyncio
- 🔄 過渡方案:等待遷移到 ASGI 的階段
適用場景
- ✅ 維護舊的 WSGI Django 專案
- ✅ 暫時無法遷移到 ASGI
- ✅ 需要快速提升舊應用並發
- ❌ 新專案(用 ASGI)
- ❌ Django 3.0+ 新功能(用 async views)
效能優勢
- 記憶體:協程比線程輕量 2000 倍
- 並發:1 worker 可處理 1000+ 連接
- 效能:I/O 密集場景可提升 10-50 倍
配置要點
- 設定
--worker-class gevent - 設定
--worker-connections 1000 - 在配置文件中加入
gevent.monkey.patch_all()
📚 接下來
現在你完全理解 Gevent Worker 了!下一篇我們會學習最後一種 Worker:
- 什麼是線程(Thread)
- Gthread Worker 如何結合進程與線程
- 適合混合型任務的場景
🤓 小測驗
Gevent Worker 的核心技術是什麼?
為什麼 Gevent 適合 I/O 密集型任務?
4 個 Gevent Workers,每個 1000 connections,總並發是多少?
為什麼 Gevent 不適合 CPU 密集型任務?
上一篇: 01-4. Sync Worker 詳解
下一篇: 01-6. Gthread Worker 詳解
最後更新:2025-10-30