根本原因是没调用 join()——主线程不等待子线程结束就直接跑完测试了;必须为每个 thread 调用 join(),避免用 sleep 替代,共享状态需用锁或 setdefault 防竞态,加锁后应删锁复测验证效果。

用 pytest + threading 模拟并发请求时,为什么 threading.Thread 启动后没执行?
常见现象是:写了多个 threading.Thread 并调用了 start(),但断言始终通过,线程函数像没运行一样。根本原因是没调用 join() —— 主线程不等待子线程结束就直接跑完测试了。
实操建议:
Python 3.14.2是Python编程语言在2025年12月5日发布的稳定版本,属于3.14系列的第二个维护更新。该版本包含了18项修复,重点解决了多进程、数据类及正则表达式等模块的回归问题,并修复了CVE-2025-12084等安全漏洞。此版本标志着自由线程模式(移除GIL)正式获得官方支持,是Python发展的重要里程碑。
- 每个
threading.Thread实例必须紧跟join(),否则测试逻辑在子线程完成前就结束了 - 避免用
time.sleep()替代join(),它不可靠且难控制精度 - 若需观察竞态发生过程,可在共享资源操作前后加
print()或日志,但记得加flush=True防止缓冲干扰判断
pytest 中如何安全地共享状态并暴露竞态?
直接用全局变量或类属性模拟共享资源最简单,但要注意 Python 的 GIL 不等于线程安全 —— 对复合操作(如 counter += 1)仍会出问题。
实操建议:
- 优先用
threading.local()做线程局部存储来对比验证:如果局部变量行为正常,而全局变量出错,基本锁定是竞态 - 用
list.append()或dict[key] = value这类看似原子的操作也要小心:它们在字节码层面可能拆成多步(如读 key、查哈希、写值),尤其在高并发下容易漏数据 - 典型易错模式:
if key not in my_dict: my_dict[key] = default—— 这是经典的“检查后设置”(check-then-set)竞态,必须用my_dict.setdefault(key, default)或加锁
怎样让 pytest 报出竞态导致的断言失败而不是静默通过?
竞态不是每次必现,单纯跑一次测试很可能漏掉问题。需要提高触发概率,同时让失败可复现。
实操建议:
- 用
@pytest.mark.parametrize控制并发数,例如从range(2, 10, 2)尝试不同线程数,小数值(如 2~4)更容易暴露问题 - 把共享状态初始化放在测试函数开头,而不是模块级 —— 避免不同测试用例相互污染
- 加入重试机制:对关键断言做有限次数循环检查(比如最多 3 次),配合短延时,能提升捕获率;但注意别让测试过长
- 不要依赖
pytest-xdist并行执行来模拟并发 —— 它是进程级并行,无法触发同一内存空间内的线程竞态
用 threading.Lock 修复后,怎么确认真的修好了?
加锁后测试通过,不代表逻辑正确。常见陷阱是锁粒度太粗(整个函数都锁)掩盖了真实问题,或锁对象用错(比如每次新建一个 Lock() 实例)。
实操建议:
- 锁对象必须是同一个实例:定义为模块变量、类属性或传入参数,不能在每次调用里
threading.Lock() - 用
with lock:而非手动acquire()/release(),防止异常跳过释放 - 修复后,删掉锁再跑一遍 —— 如果又失败,说明锁确实起了作用;如果依然通过,可能是原来就没问题,或竞态被其他因素掩盖
- 注意死锁风险:多个锁嵌套时,务必按固定顺序获取;单个锁一般无此问题,但复杂场景建议用
threading.RLock替代
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










