在 Python 中,异步编程和协程技术已经广泛应用于网络爬虫、IoT 设备数据处理、Web 服务等场景。本文将从基础原理、gevent/greenlet、monkey.patch_all 的作用、阻塞函数调度问题到实际应用案例,全面讲解 Python 异步实践。
一、Python 异步基础
Python 自 3.4 版本引入了 asyncio,3.5 版本开始支持 async / await 语法,用于处理 IO 密集型任务。
python
import asyncio
async def hello():
print("Hello")
await asyncio.sleep(1)
print("World")
asyncio.run(hello())
注意:
asyncio只能调度异步函数或 IO,可以通过await让事件循环切换。纯阻塞函数无法被 asyncio 调度。
二、Greenlet 与 Gevent
2.1 Greenlet
-
Greenlet 是一个 轻量级协程库。
-
它可以在 Python 函数之间 手动切换执行。
-
特点:
- 只负责"切换执行权",不做 IO 调度。
- 非阻塞函数之间切换非常快。
python
from greenlet import greenlet
def task1():
print("Task 1 start")
gr2.switch()
print("Task 1 end")
def task2():
print("Task 2 start")
gr1.switch()
print("Task 2 end")
gr1 = greenlet(task1)
gr2 = greenlet(task2)
gr1.switch()
2.2 Gevent
-
Gevent 是 基于 Greenlet 的协程框架。
-
它把 Greenlet + IO 自动调度 结合起来:
- 对标准库中的阻塞 IO(socket、time.sleep 等)进行 monkey patch。
- 当一个 greenlet 执行阻塞 IO 时,自动切换到其他 greenlet。
python
import gevent
from gevent import monkey
monkey.patch_all() # 改写阻塞 IO
def task1():
print("Task 1 start")
gevent.sleep(1)
print("Task 1 end")
def task2():
print("Task 2 start")
gevent.sleep(1)
print("Task 2 end")
gevent.joinall([
gevent.spawn(task1),
gevent.spawn(task2)
])
2.3 Greenlet 与 Gevent 的关系
| 组件 | 功能 |
|---|---|
| Greenlet | 轻量级协程,只切换执行权,不调度 IO |
| Gevent | 基于 Greenlet,自动调度 IO + 事件循环 + monkey patch |
| monkey.patch_all() | 改写标准库阻塞函数,让 Gevent 可以调度 |
一句话理解:Greenlet 是骨架,Gevent 给骨架加了心跳(IO 调度和事件循环)。
三、monkey.patch_all() 原理
- 对标准库的阻塞函数(如 socket、ssl、time.sleep、threading)进行替换
- 替换后的函数会在阻塞时让出控制权,切换到其他 greenlet
- 适用于 IO 密集型操作,无法影响纯 CPU 密集型或 C 扩展阻塞函数
示例:
python
from gevent import monkey
monkey.patch_all()
import socket
s = socket.socket() # 实际上被 gevent.socket 替换
四、阻塞函数与异步调度
Python 中 C 扩展阻塞函数(如 pandas.read_excel()、numpy 数值计算)不会触发协程调度。
python
import pandas as pd
df = pd.read_excel("data.xlsx") # 阻塞操作,无法被 asyncio 或 gevent 调度
解决方案:使用线程池或进程池
python
import pandas as pd
import asyncio
from concurrent.futures import ThreadPoolExecutor
executor = ThreadPoolExecutor()
async def read_excel_async(path):
loop = asyncio.get_event_loop()
return await loop.run_in_executor(executor, pd.read_excel, path)
async def main():
df = await read_excel_async("data.xlsx")
print(df)
asyncio.run(main())
优点:事件循环不被阻塞,pandas 执行在独立线程中。
五、总结
- asyncio 适用于 IO 协程,无法调度阻塞 C 扩展
- gevent 通过 monkey.patch_all() 改写标准库 IO 实现协作式调度
- CPU 密集型或 C 扩展阻塞函数需放线程池/进程池
- pandas.read_excel 可通过
run_in_executor异步读取 - Greenlet 是 Gevent 的核心协程骨架,Gevent 提供 IO 调度和事件循环,使协程可以自动切换