Eventlet猴子补丁是否启动多线程而非协程?是否存在竞态条件?
Eventlet猴子补丁后thread.ident变化的疑问解答
嘿,我来帮你捋清楚这个问题哈!首先明确说:这并不意味着猴子补丁启动的是多线程,本质还是协程,之所以thread.ident输出不同,是因为Eventlet的猴子补丁替换了标准库的thread模块相关实现——打补丁后,thread.ident返回的已经不是操作系统级别的线程ID,而是Eventlet自己维护的协程(greenlet)的唯一标识。
为什么两种测试场景输出差异这么大?
- 没调用
monkey_patch()时,你用的是Python原生的阻塞API,比如time.sleep()会直接阻塞整个线程,eventlet的协程没法进行切换,所以所有任务都串行跑在同一个主线程里,自然thread.ident输出的都是同一个真实线程ID。 - 打了猴子补丁后,Eventlet把标准库里的阻塞API(比如
time.sleep()、socket操作等)都替换成了自己的协作式实现,当协程执行到这些操作时会主动让出CPU,切换到其他协程。这时候thread.ident被替换成了每个协程的唯一标识,所以每次输出不同,但这些协程其实都运行在同一个操作系统线程里,完全不是多线程。
给你补全两种场景的测试代码,看得更清楚:
未打猴子补丁的测试代码
#!/usr/bin/env python # encoding: utf-8 import eventlet import thread import time def test(): print(f"Thread ident: {thread.ident}") time.sleep(1) # 原生sleep,阻塞整个线程 # 未打补丁,协程无法切换,串行执行 for _ in range(3): eventlet.spawn(test).wait()
这段代码里三个任务会依次执行,每次输出的thread.ident完全相同。
打了猴子补丁的测试代码
#!/usr/bin/env python # encoding: utf-8 import eventlet eventlet.monkey_patch() # 启用猴子补丁 import thread import time def test(): print(f"Thread ident: {thread.ident}") time.sleep(1) # 被替换为eventlet的协作式sleep,触发协程切换 # 打补丁后协程可以切换,并发执行 tasks = [eventlet.spawn(test) for _ in range(3)] eventlet.sleep(2) # 等待所有任务完成
这里三个任务会并发执行,每个任务对应一个协程,thread.ident输出的是协程的唯一ID,所以各不相同,但它们都跑在同一个OS线程中。
关于竞态条件的疑问
Eventlet的协程是协作式调度,同一时间只有一个协程在运行,只有当协程执行到触发切换的操作(比如eventlet.sleep()、网络IO、主动调用eventlet.yield()等)时,才会切换到其他协程。
所以:
- 如果你的代码里没有这些切换触发点,多个协程不会同时操作共享资源,也就不会出现竞态条件;
- 但如果有共享资源,且在协程切换前后对资源进行读写操作,还是需要用同步原语(比如eventlet提供的
Semaphore)来保护,不过这种情况的风险比多线程的抢占式调度低很多——毕竟你能精准控制协程切换的时机。
总结一下:猴子补丁后还是协程,thread.ident只是被替换成了协程ID;竞态条件有一定风险,但和多线程的情况完全不同,只要合理处理切换点就能有效规避。
内容的提问来源于stack exchange,提问作者cosz3
相关产品推荐
相关产品推荐

