Python并发安全问题核心是多线程/协程访问共享资源时未加控制导致数据不一致;常见风险资源包括全局变量、类属性、文件句柄、数据库连接等;识别关键在“读-改-写”模式与可变共享对象;防护需按场景选Lock、Semaphore或Queue,并优先减少共享。

Python中并发安全问题的核心是多个线程或协程同时访问共享资源时,未加控制导致数据不一致或逻辑错误。虽然GIL限制了CPython中多线程的CPU并行,但I/O密集型场景下线程仍会频繁切换,资源竞争依然真实存在;异步协程(如asyncio)则完全不受GIL约束,竞争风险更高。
哪些资源容易引发竞争?
常见共享资源包括:全局变量、类实例属性、模块级字典/列表、文件句柄、数据库连接、缓存对象(如Redis客户端)、计数器等。只要多个并发单元(线程/协程)可能读写同一内存地址或外部状态,就存在竞争可能。
- 例如:多个线程对一个全局计数器 count += 1,看似原子,实际包含读取、计算、写入三步,中间可能被抢占
- 又如:两个协程同时调用 cache.set("key", value),若底层实现非线程/协程安全,可能覆盖或丢失更新
如何识别潜在竞争点?
关注代码中出现“读-改-写”模式的位置(如 data.append(x)、config['timeout'] += 1、if flag: do_something() 后再修改 flag),以及任何跨并发单元共享的状态变更。
- 检查是否使用了可变对象(list/dict/set)作为共享容器
- 留意第三方库文档是否明确声明“线程安全”或“协程安全”——多数纯Python工具默认不保证
- 日志中反复出现不一致值(如统计总数远小于预期、缓存命中率异常波动)往往是竞争的间接信号
常用防护手段与选择建议
防护不是越重越好,需匹配场景复杂度和性能要求:
立即学习“Python免费学习笔记(深入)”;
- 线程场景:优先用 threading.Lock 或 threading.RLock 包裹临界区;高频小操作可用 queue.Queue 替代手动同步
- 协程场景(asyncio):避免用 threading 模块锁(会阻塞事件循环),改用 asyncio.Lock、asyncio.Semaphore;共享状态尽量通过 asyncio.Queue 传递
- 通用技巧:减少共享——用局部变量+参数传递代替全局状态;用不可变数据结构(如 types.MappingProxyType)限制意外修改;对简单计数考虑 threading.local() 或原子操作(如 concurrent.futures 的回调机制)
别忽略“伪安全”的陷阱
有些写法看似无害,实则隐藏风险:
- if key not in dict: dict[key] = value —— 非原子,两个线程可能同时通过判断后写入,后者覆盖前者
- list.sort() 或 dict.clear() 在多线程中调用,即使不与其他操作组合,也可能因内部实现被中断而引发异常或数据损坏
- logging.info() 本身线程安全,但格式化字符串中的表达式(如 f"count={shared_list.len()}")不在保护范围内,len() 调用仍可能被抢占
不复杂但容易忽略。关键在写并发代码前,先问一句:这个变量/对象,会不会被别人同时碰?碰了会怎样?想清楚再加锁,比事后调试快得多。










