asyncio.lock只能用于异步协程,不能在同步代码或线程中直接使用,因其依赖事件循环调度机制,跨协程释放会导致状态错乱,且不保证获取顺序,需严格配对acquire/release。

asyncio.Lock 为什么不能用在同步代码里
它根本不是线程锁,而是协程调度层面的协作式锁。你在 threading.Thread 里 await lock.acquire() 会直接报 RuntimeError: cannot be used from outside a coroutine,因为底层依赖事件循环和挂起/恢复机制。
- 同步函数里调用
lock.acquire()(没加await)会返回一个协程对象,不 await 就等于“忘了执行”,锁永远拿不到 - 跨线程使用(比如从另一个线程调用
loop.call_soon_threadsafe(lock.release))可以,但必须确保锁是在该 loop 创建的;否则抛RuntimeError: Lock is not acquired - 它不阻塞线程,只让当前协程让出控制权——所以 CPU 密集型任务里加了也没用,得配合
asyncio.to_thread()或loop.run_in_executor()
多个协程竞争时 acquire() 的排队顺序不可靠
asyncio.Lock 内部用的是 FIFO 队列,但“谁先等到”不等于“谁先发起请求”。如果一批协程几乎同时 await lock.acquire(),实际唤醒顺序受事件循环调度、await 表达式求值时机、甚至 Python 版本影响。
- Python 3.11+ 在某些场景下会优化 await 链,导致看似后发的协程反而先被唤醒
- 不要依赖“第 N 个 await 的一定第 N 个拿到锁”,尤其在测试中用
time.sleep(0.001)模拟时序,结果很可能在 CI 环境翻车 - 真要严格保序,得自己套一层带序号的队列管理,或者改用
asyncio.Queue(maxsize=1)做信号量替代
lock.release() 必须由 acquire() 的同一协程调用
这是最容易踩的坑:lock.release() 不检查调用者身份,但若由别的协程释放,会导致状态错乱——比如锁明明没被持有却成功 release,后续 acquire() 可能立刻返回,彻底失效。
- 常见错误:在
try/finally外层用async with lock:很安全;但手写时漏掉finally,或把release()放到回调、task.done() 回调里,就可能跨协程释放 -
async with lock:是最简方案,它自动绑定 acquire/release 到同一协程栈帧 - 调试时可临时给 lock 加个
_owner属性(仅用于开发),在acquire()存id(asyncio.current_task()),release()时校验,上线前删掉
和 threading.Lock 混用会引发死锁或静默失败
两者完全不兼容。你不能把 asyncio.Lock 传进 concurrent.futures.ThreadPoolExecutor 的函数里,也不能在同步回调里 await 它。
立即学习“Python免费学习笔记(深入)”;
- 典型错误:用
loop.run_in_executor(None, sync_func, lock),然后在sync_func里await lock.acquire()—— 同步函数里 await 语法错误,直接SyntaxError - 更隐蔽的:在 executor 里调用
lock.locked()是允许的(它只是读状态),但如果你接着在 executor 里调用lock.release(),运行时不会报错,但锁状态会崩坏 - 正确做法:需要跨线程同步访问共享资源时,用
threading.Lock+loop.call_soon_threadsafe()通知协程侧;纯异步场景,别碰 threading 模块










