从原理上说 Python 的协程是运行在单进程单线程上的,是协作式调度,因此不存在 race conditon,那么这个协程锁是干啥的呢?
我研究了下,发现还是有其使用场景的。
发现问题
Python 协程在遇到 await 时会显式切换,这种切换类似于 GIL 锁的切换,如果是非原子性操作,就会出现数据或操作顺序与预期不一致的现象。
import asyncio
a = [0]
async def ():
await asyncio.sleep(0.00001)
async def add():
b = a[0]
await f()
b += 1
a[0] = b
async def main():
coros = [add() for _ in range(100)]
await asyncio.gather(*coros)
asyncio.run(main())
print(a[0])
预期是 100,实际是 1。因为协程 1 读到的 b 为 0,遇到 await 切换到 f(),再切回到协程 2,读到的 b 为 0,遇到 await 切换到 f(),再切回到协程 3,读到的 b 为 0,当第 100 个协程切回到 f(),最后切回协程 99,此时 b+1 得到 1,再切回协程 98,此时 b+1 得到 1,最终 a[0]=1。
解决问题
加锁可以将并发变成串行,结果自然就是 100。
import asyncio
a = [0]
lock = asyncio.Lock()
async def ():
await asyncio.sleep(0.00001)
async def add():
async with lock:
b = a[0]
await f()
b += 1
a[0] = b
async def main():
coros = [add() for _ in range(100)]
await asyncio.gather(*coros)
asyncio.get_event_loop().run_until_complete(main())
print(a[0])
asyncio.run(main()) 在上面运行不了,会提示如下,Linux 试了也一样,看来 Python 3.7 的 asyncio.run() 并不好用。
RuntimeError: Task cb=[gather.._done_callback() at D:ProgramDevsPythonPython37libasynciotasks.py:664]> got Future attached to a different loop
使用场景
对协程的执行顺序有要求,但执行代码非原子操作,且被 await 语句隔开。
其他锁
当然 asyncio 还实现了其他的锁,和线程锁的接口是一样的,具体使用场景参阅文档。