Python 异步编程与 Gevent 实战指南

在 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 执行在独立线程中。


五、总结

  1. asyncio 适用于 IO 协程,无法调度阻塞 C 扩展
  2. gevent 通过 monkey.patch_all() 改写标准库 IO 实现协作式调度
  3. CPU 密集型或 C 扩展阻塞函数需放线程池/进程池
  4. pandas.read_excel 可通过 run_in_executor 异步读取
  5. Greenlet 是 Gevent 的核心协程骨架,Gevent 提供 IO 调度和事件循环,使协程可以自动切换

相关推荐
智算菩萨3 分钟前
MP3音频编码原理深度解析与Python全参数调优实战:从心理声学模型到LAME编码器精细控制
android·python·音视频
qq_4523962332 分钟前
【模型手术室】第四篇:全流程实战 —— 使用 LLaMA-Factory 开启你的第一个微调任务
人工智能·python·ai·llama
无心水1 小时前
Java时间处理封神篇:java.time全解析
java·开发语言·python·架构·localdate·java.time·java时间处理
吴秋霖2 小时前
【某音电商】protobuf聊天协议逆向
python·算法·protobuf
深藏功yu名2 小时前
Day24:向量数据库 Chroma_FAISS 入门
数据库·人工智能·python·ai·agent·faiss·chroma
cm6543202 小时前
用Python破解简单的替换密码
jvm·数据库·python
wan9yu2 小时前
为什么你需要给 LLM 的数据"加密"而不是"脱敏"?我写了一个开源工具
python
摇滚侠2 小时前
你是一名 java 程序员,总结定义数组的方式
java·开发语言·python
这个名有人用不3 小时前
解决 uv 虚拟环境使用 pip 命令提示command not found的办法
python·pip·uv·claude code