asyncio.lock()必须await才生效,直接调用acquire()不阻塞;正确用法是await lock.acquire()或async with lock:,且不可跨线程使用,仅适用于同event loop内的异步上下文。

asyncio.Lock() 必须 await 才生效,直接调用不阻塞
很多人写 lock.acquire() 就以为加锁了,结果发现根本没用——因为 acquire() 返回的是一个协程对象,不 await 它,锁压根没拿到。这是最常踩的坑,现象是:多个协程同时进临界区,数据依然错乱。
- 正确写法永远是
await lock.acquire(),然后手动lock.release() - 更安全的做法是用
async with lock:,它自动处理 acquire/release,即使抛异常也保证释放 -
lock.locked()可以检查当前是否已被占用,但不能代替 await —— 它只反映状态,不参与调度
asyncio.Lock() 不能跨线程使用,别和 threading.Lock 混用
如果你在异步函数里混用了 threading.Lock() 或试图把 asyncio.Lock() 传给 loop.run_in_executor() 里的同步代码,会直接报错或死锁。asyncio 的锁只在同一个 event loop 内有效,它依赖协程调度,和线程无关。
- 需要保护的是纯异步上下文中的共享变量(比如全局 dict、类属性、缓存)
- 如果临界区里要调用同步阻塞函数(如文件读写、requests.get),必须用
loop.run_in_executor()包裹,且同步部分用threading.Lock()单独保护 - 常见错误:在
run_in_executor回调里还去awaitasyncio.Lock()—— 这时已经不在 event loop 线程里了,会报RuntimeError: no running event loop
锁粒度太粗会导致 async 并发优势消失
用一个全局 asyncio.Lock() 把整个数据处理流程包住,看起来安全了,但实际把异步协程串行化了,吞吐量可能比同步还差。asyncio 的价值在于 I/O 等待时让出控制权,锁一卡住,其他协程就全等着。
- 优先按资源维度拆锁:比如每个用户 ID 对应一个独立
asyncio.Lock()实例,而不是一个锁管所有用户 - 避免在锁内做 await(除非你明确知道那不是 I/O,比如只是算数);否则可能造成锁持有时间远超预期
- 注意锁的生命周期:临时创建的锁(比如在函数内
lock = asyncio.Lock())对并发无意义——每次都是新锁,起不到互斥作用
asyncio.Lock() 不支持超时重试逻辑,得自己封装
标准 asyncio.Lock() 没有内置 timeout 参数,await lock.acquire() 会一直等下去。生产环境里,如果某个协程持锁崩溃或卡死,其他协程就会无限等待。
- 可用
asyncio.wait_for(lock.acquire(), timeout=2)包一层,捕获asyncio.TimeoutError后做降级或告警 - 不要在
async with里直接套wait_for,因为async with的 enter 阶段无法中断;得先await wait_for(...),再进入async with - 如果需要“尝试获取,失败就跳过”,用
if not lock.locked(): await lock.acquire()不可靠——两个协程可能同时通过locked()判断,仍会冲突;必须靠acquire()的原子性
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











