greenlet 是一个轻量级的协程库,它让你可以手动控制代码的执行流,实现任务的切换,它是理解更高级的协程库(如 gevent)和 Python 原生 asyncio 的基础。

核心概念:什么是 Greenlet?
你可以把一个 greenlet 想象成一个极小的、可以随时暂停和恢复的“线程”,它不是操作系统级别的线程,而是由用户代码在 Python 解释器层面控制的。
关键特性:
- 手动切换:与
asyncio不同,greenlet的切换不是由事件循环自动驱动的,而是需要你显式地调用greenlet.switch()方法来切换执行权。 - 单线程:所有 greenlet 都在同一个操作系统线程内运行,因此它们的切换不涉及操作系统的上下文切换,开销非常小。
- 协作式多任务:一个 greenlet 只有在主动
switch出去时,才会让出 CPU,其他 greenlet 才有机会运行,如果一个 greenlet 陷入了死循环,整个程序都会被阻塞。
安装 Greenlet
你需要安装它:
pip install greenlet
基本使用:创建和切换
让我们从一个最简单的例子开始,创建两个 greenlet 并在它们之间切换。

from greenlet import greenlet
def test1():
print("Test1: 我是 test1,准备切换到 test2")
# 切换到 g2,并可以传递数据
gr2.switch("来自 test1 的问候")
print("Test1: 我又回来了,来自 test2 的数据是:", gr2.dead) # gr2.dead 检查 g2 是否已结束
def test2():
# 接收来自 g1.switch() 的数据
msg = gr1.switch("来自 test2 的问候")
print("Test2: 我是 test2,收到了消息:", msg)
print("Test2: 我要结束了")
# 创建两个 greenlet 对象
# g1 会执行 test1 函数
# g2 会执行 test2 函数
gr1 = greenlet(test1)
gr2 = greenlet(test2)
# 启动 g1,test1 开始执行
# 第一次 switch 时,可以传递数据给 test1
print("主线程:启动 g1")
gr1.switch("主线程的初始数据")
print("主线程:g1 和 g2 都执行完毕")
输出结果:
主线程:启动 g1
Test1: 我是 test1,准备切换到 test2
Test2: 我是 test2,收到了消息: 来自 test1 的问候
Test2: 我要结束了
Test1: 我又回来了,来自 test2 的数据是: True
主线程:g1 和 g2 都执行完毕
代码解析:
greenlet(test_func)创建一个新的 greenlet 对象,它会在被switch到时执行test_func函数。gr.switch(value)是核心操作:- 第一次调用:启动 greenlet
gr,并开始执行其关联的函数。value会被作为函数的返回值(而不是参数)。 - 后续调用:暂停当前 greenlet 的执行,跳转到
gr的暂停点(或起始点)继续执行。value同样是作为gr函数的返回值。 - 当
gr函数执行完毕或gr被kill()后,switch(gr)会得到None。
- 第一次调用:启动 greenlet
gr.dead属性:greenlet 已经执行完毕,则返回True,否则返回False。
Greenlet 的生命周期和父子关系
Greenlet 之间可以形成类似“调用栈”的父子关系。
- 父 Greenlet:当一个 greenlet
A创建了另一个 greenletB,AB的父 greenlet。 g.parent:可以访问或设置一个 greenlet 的父 greenlet。g.switch()和父 Greenlet:如果一个 greenlet 执行完毕,它会自动切换回它的父 greenlet。g.throw():向 greenletg抛入一个异常,使其在switch到它时执行。
示例:父子关系和自动切换

from greenlet import greenlet
def child():
print("子 Greenlet: 开始执行")
print("子 Greenlet: 我要结束了,会自动切换回父 Greenlet")
# 函数执行完毕,自动切换回 parent
def parent():
print("父 Greenlet: 创建子 Greenlet")
ch = greenlet(child)
print("父 Greenlet: 切换到子 Greenlet")
ch.switch() # 切换到 ch
print("父 Greenlet: 从子 Greenlet 返回,继续执行")
# 主线程是 parent 的父 greenlet
p = greenlet(parent)
p.switch()
输出结果:
父 Greenlet: 创建子 Greenlet
父 Greenlet: 切换到子 Greenlet
子 Greenlet: 开始执行
子 Greenlet: 我要结束了,会自动切换回父 Greenlet
父 Greenlet: 从子 Greenlet 返回,继续执行
实际应用场景:模拟并发任务
greenlet 本身不提供 I/O 多路复用,所以不能直接用它来做异步网络编程,但它非常适合模拟那些需要“协作式”调度的任务,比如游戏逻辑、状态机等。
示例:简单的任务调度器
from greenlet import greenlet
import time
def task(name, count):
for i in range(count):
print(f"任务 {name} 正在执行,计数: {i}")
# 模拟一个耗时操作,这里我们手动切换
time.sleep(0.1) # 注意:time.sleep 会阻塞整个线程!
# 在真实场景中,这里应该是一个可以被 greenlet 感知的 I/O 操作
# 然后我们手动切换到下一个任务
scheduler.switch()
def scheduler():
tasks = [
greenlet(task, ("任务A", 5)),
greenlet(task, ("任务B", 5)),
greenlet(task, ("任务C", 5))
]
while tasks:
# 轮询执行每个任务
for t in tasks:
if not t.dead:
t.switch()
# 移除已完成的任务
tasks = [t for t in tasks if not t.dead]
print("调度器:一轮任务结束,准备下一轮...")
time.sleep(0.5)
print("主线程:启动调度器")
scheduler = greenlet(scheduler)
scheduler.switch()
print("主线程:所有任务完成")
输出结果:
主线程:启动调度器
任务 任务A 正在执行,计数: 0
任务 任务B 正在执行,计数: 0
任务 任务C 正在执行,计数: 0
调度器:一轮任务结束,准备下一轮...
任务 任务A 正在执行,计数: 1
任务 任务B 正在执行,计数: 1
任务 任务C 正在执行,计数: 1
调度器:一轮任务结束,准备下一轮...
...
注意:这个例子中的 time.sleep() 会阻塞整个线程,导致其他 greenlet 也无法运行,这只是为了演示切换逻辑,真正的 greenlet 应用场景是配合一个能处理阻塞 I/O 的库(如 gevent)。
Greenlet vs. asyncio vs. Threading
| 特性 | greenlet |
asyncio |
threading |
|---|---|---|---|
| 控制方式 | 手动 (switch) |
自动 (事件循环) | 抢占式 (操作系统) |
| 并发模型 | 协作式多任务 | 协作式多任务 | 抢占式多任务 |
| 开销 | 极低 (纯Python切换) | 低 (用户态切换) | 高 (内核态切换) |
| 适用场景 | 理解协程、构建框架、状态机 | 高性能网络I/O、并发任务 | CPU密集型任务、并行计算 |
| GIL | 受GIL限制 | 受GIL限制 | 受GIL限制(但可利用多核) |
| 数据共享 | 需注意线程安全 | 需注意线程安全 | 需注意线程安全 |
重要注意事项
- 不是万能药:
greenlet不能绕过 Python 的 GIL,对于 CPU 密集型任务,多线程或多进程仍然是更好的选择。 - 手动切换的复杂性:手动管理
switch会让代码变得复杂,难以维护,这就是为什么现代 Python 更推荐使用asyncio这样的自动事件循环库。 gevent的强大:gevent是greenlet的一个强大封装,它通过monkey-patching(猴子补丁)技术,将 Python 标准库中的阻塞 I/O 操作(如socket,threading等)自动替换为非阻塞版本,这样,你可以在gevent的世界里,用同步的代码风格写出异步的 I/O 程序,而无需手动switch。
greenlet 是一个非常基础和底层的协程实现,学习它有助于你深入理解协程的工作原理,特别是任务切换的机制。
你应该什么时候用 greenlet?
- 学习目的:为了理解协程的底层实现。
- 框架开发:如果你正在开发一个需要任务调度功能的框架,
greenlet提供了底层的构建块。 - 特定场景:在那些逻辑上天然适合“协作式”调度的场景,如复杂的游戏状态机。
对于绝大多数应用开发,特别是网络编程,你更应该使用基于 greenlet 的 gevent 或者 Python 原生的 asyncio,它们提供了更高层次的抽象,让你能更轻松地编写高效的并发代码。
