发布于2026-07-18 阅读(0)
扫一扫,手机访问
在异步编程中,变量竞争是个老生常谈的问题。asyncio.Lock 通过协程排队确保临界区原子性,避免异步变量竞争;使用时需要全局单例、用 async with 自动管理、禁用嵌套加锁,而且不能与 threading.Lock 混用。下面从原理到实践,逐一拆解。

异步任务共享变量时,await 可能打断执行流,导致多个协程在未完成读-改-写前交叉执行。比如对一个整数做 counter += 1,底层是“读值→计算→写回”三步,中间被切走就会丢更新。asyncio.Lock 不是阻塞线程,而是让协程排队——只有拿到锁的协程能进临界区,其他协程挂起等待,从而保证这段逻辑原子性。
常见错误是忘记 await lock.acquire() 或漏掉 lock.release(),更稳妥的是用 async with lock: 自动管理生命周期。注意:锁对象必须是同一个实例,不能每次 new 一个;且不能在非 async 函数里调用 await。
lock = asyncio.Lock() 必须定义在协程外部(如全局、类属性或 event loop 启动前),否则每个协程拿的是不同锁try/except 中手动 acquire/release,除非你明确处理异常后释放——推荐统一用 async with lock:asyncio.Lock 不支持重入,重复 await 同一把锁会死锁threading.Lock 是线程级同步原语,用在多线程中;asyncio.Lock 是协程级,只在同一个 event loop 内生效。混用会出问题:比如在 loop.run_in_executor 里用 asyncio.Lock 就无效,因为那是在新线程里跑的同步代码,得换 threading.Lock 或用 asyncio.to_thread + 线程锁配合。
await lock.acquire() 会永远挂起(不可重入)asyncio.Lock 不阻塞 OS 线程,只是让协程让出控制权,所以不会拖慢整个 loop下面这个例子演示了没锁 vs 有锁的区别。去掉 async with lock:,最终 counter 很可能小于 100;加上之后稳定输出 100。
import asyncio
counter = 0
lock = asyncio.Lock() # 全局单例
async def increment():
global counter
for _ in range(10):
async with lock: # 关键:确保 += 原子执行
counter += 1
async def main():
await asyncio.gather(*[increment() for _ in range(10)])
asyncio.run(main())
print(counter) # 输出 100
真正容易被忽略的是:锁粒度要匹配业务语义。比如你要保护的是“查库存→扣减→写日志”整个流程,那就得把这三步全包进 async with lock:,而不是只锁其中一步。不然还是会有状态不一致风险。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8