threading.Lock在asyncio中会卡死事件循环:因其阻塞OS线程,导致单线程事件循环停摆,所有协程冻结;必须改用awaitable的asyncio.Lock,二者底层机制完全不同,不可混用。

threading.Lock 会直接卡死整个事件循环
它不是“不推荐”,而是物理层面不可行:一旦协程里调用 threading.Lock.acquire(),当前 OS 线程就被阻塞,而 asyncio 默认只跑在一个线程里。事件循环(event loop)随之停摆,所有协程冻结,包括那个本该释放锁的协程——死锁当场成立。
常见错误现象:
- 程序突然无响应,
asyncio.run()卡住,连Ctrl+C都收不到信号(需kill -9) - 日志停在某条 “尝试获取锁” 后再无输出
- 超时设置(如
asyncio.wait_for)也失效,因为事件循环已无法推进
asyncio.Lock 和 threading.Lock 根本不是同一套机制
两者名字像,但底层完全不兼容:
-
asyncio.Lock是纯 Python 实现的协程调度逻辑,await lock.acquire()只是把当前协程挂起、加入等待队列,控制权立刻交还给事件循环 -
threading.Lock是 CPython 调用操作系统 mutex 的同步原语,必须等持有者释放,期间线程休眠,不参与任何协程调度 - 它们不能混用:你不能在
async with threading.Lock()里 await,也不能把asyncio.Lock传进run_in_executor当同步锁用
哪些场景容易误用 threading.Lock?
开发者常在这些地方“顺手就写错”:
立即学习“Python免费学习笔记(深入)”;
- 想保护一个全局计数器,直接套
with threading.Lock():—— 错,应改用async with asyncio.Lock(): - 封装了一个异步类(比如
AsyncCache),内部却用threading.Lock保护字典读写 —— 错,即使单线程也不行 - 在
async def函数里调用某个第三方同步库的加锁方法(如某些老版本数据库驱动),没意识到那本质是threading.Lock—— 错,应确认该库是否提供 async 接口,或改用loop.run_in_executor隔离
asyncio.Lock 不是万能解药,关键在识别临界区
最常被忽略的一点:用了 asyncio.Lock 并不自动等于线程安全,也不等于业务逻辑正确。它只保证“同一时刻最多一个协程执行临界区代码”。如果临界区里干了不该干的事,照样出问题:
- 在
async with lock:里调用time.sleep(1)—— 整个事件循环卡死 1 秒 - 锁的范围过大,比如把整个 HTTP 请求+解析+写 DB 都包进去,严重拖慢并发吞吐
- 多个不同锁之间存在嵌套或交叉顺序,可能引发死锁(虽然不常见,但
acquire()+release()手动管理时容易出)
真正要问的不是“要不要加锁”,而是“这段逻辑里,哪些共享状态的不变量会被 await 拆开?谁在读、谁在写、中间能不能被插队?”——答案明确了,锁才用得准。


















