死锁和竞态条件是python并发中最易出问题的两个点:竞态条件源于非原子操作(如counter += 1)被中断,需用lock或线程安全结构防护;死锁由循环等待锁引发,须统一加锁顺序、设超时并避免嵌套锁;异步环境下需用asyncio.lock而非threading.lock,且i/o密集用asyncio或threading,cpu密集必须用multiprocessing。

Python并发中,死锁和竞态条件是最容易出问题的两个点。它们不总在测试时暴露,却常在线上突然导致数据错乱或服务卡死。关键不是“会不会用线程”,而是“共享资源怎么管”。
竞态条件:看似简单的 += 其实很危险
多个线程同时执行 counter += 1,结果常常小于预期值——这不是运气差,是操作本身非原子:读取、计算、写回三步之间可能被切换打断。
- 别依赖 GIL 保护:GIL 只保证字节码级互斥,不能覆盖多步逻辑
- 简单变量用
threading.Lock包裹临界区,推荐with lock:写法,自动释放 - 优先选线程安全结构:比如用
queue.Queue传数据,而不是直接改全局字典;用threading.local()给每个线程配独立副本 - 避免跨线程共享可变对象(如 list、dict),尤其不要让一个线程 append,另一个线程 pop
死锁:两个线程互相等对方放手
典型场景是转账:线程A锁账户X再等Y,线程B锁账户Y再等X。一旦发生,程序就停在那里,不报错也不继续。
- 统一加锁顺序:所有线程按相同规则获取锁,比如总是先锁 ID 小的账户
- 避免嵌套锁:一个函数里不要持有一个锁再去申请另一个;真需要,用
threading.RLock(可重入),但不解决根本问题 - 设超时:
lock.acquire(timeout=2),失败就退避或重试,别干等 - 用工具辅助检测:开发阶段可启用
threading.settrace或静态分析工具检查锁路径
异步环境也有对应问题
asyncio 里没有线程,但协程共享变量一样会乱。比如多个协程同时更新缓存计数器,结果对不上。
立即学习“Python免费学习笔记(深入)”;
- 别在 async 函数里用
threading.Lock:它会阻塞事件循环,整个程序卡住 - 改用
asyncio.Lock,配合async with lock:,挂起协程而非阻塞线程 - 文件写、数据库初始化、单例加载这类操作,只要涉及共享状态,都得加异步锁
- 注意 awaitable 资源:比如用
aiomysql替代mysql-connector,否则同步调用照样拖垮 event loop
选对模型比修 bug 更重要
很多“并发问题”其实源于模型误用。GIL 不是 bug,是设计约束;用错地方才成坑。
- CPU 密集任务(如图像处理、数值计算):放弃 threading,改用
multiprocessing或concurrent.futures.ProcessPoolExecutor - I/O 密集任务(如 HTTP 请求、日志写入):threading 有效,因为等待时 GIL 自动释放;高并发场景可切 asyncio
- 混合任务(部分计算 + 部分请求):用
ProcessPoolExecutor做计算,ThreadPoolExecutor或 asyncio 做 I/O,明确分工 - 别在协程里调
time.sleep()、requests.get()、open()这类同步函数,必须用asyncio.sleep()、aiohttp、aiosqlite等异步替代,或包进loop.run_in_executor










