基于CPython的Python asyncio原子执行单元技术问询
Python asyncio 的原子执行单元是什么?
在asyncio的协作式调度模型里,协程的执行会一直持续到遇到显式的异步操作(比如await、asyncio.sleep()、asyncio.wait()等)才会暂停,这段从一个异步让出点到下一个异步让出点之间的代码,就是asyncio提供的原子执行单元——也就是说,这段代码会被完整执行,不会被其他协程打断。
为什么是这样?
asyncio基于单线程事件循环运行,协程之间是协作式调度,而非线程的抢占式调度:
- 线程依赖GIL,在每个字节码执行完成后都可能被调度切换,所以单个字节码是原子的,但跨多个字节码的操作可能被打断。
- 协程则完全不同,只有当代码主动调用
await这类会触发事件循环调度的操作时,当前协程才会让出CPU,让其他协程运行。在没有这类操作的代码段里,不管包含多少字节码,都会一次性执行完毕,不会被其他协程干扰。
举个例子
下面的代码中,increment函数里没有任何await操作,所以每个协程的temp = x和x = temp +1会被完整执行,最终结果一定是1000:
import asyncio x = 0 async def increment(): global x temp = x x = temp + 1 async def main(): tasks = [increment() for _ in range(1000)] await asyncio.gather(*tasks) print(x) # 输出 1000 asyncio.run(main())
但如果在temp = x之后加一行await asyncio.sleep(0)(主动让出控制权),结果就会变成不确定的数值——因为协程在执行到await时暂停,其他协程会修改x,破坏了操作的原子性:
import asyncio x = 0 async def increment(): global x temp = x await asyncio.sleep(0) # 主动让出,原子性被打破 x = temp + 1 async def main(): tasks = [increment() for _ in range(1000)] await asyncio.gather(*tasks) print(x) # 输出小于1000的随机数 asyncio.run(main())
注意事项
不要依赖这种原子执行单元来处理竞态条件,因为一旦代码中不小心引入了await(哪怕是调用了其他含await的函数),原子性就会被打破。正确的做法是使用asyncio提供的同步原语,比如asyncio.Lock,来保护共享资源的访问。
内容的提问来源于stack exchange,提问作者김꼬막
相关产品推荐
相关产品推荐

