Python模块中多线程调用worker()函数是否线程安全?
线程安全疑问:模块级变量的延迟初始化
我在Python模块里写了类似这样的代码:
_x = None def set_x(): global _x _x = # some value (will always be the same) def worker(): if _x is None: set_x()
模块的客户端会创建多个线程调用worker(),想请教下这种写法线程安全吗?
补充几个背景信息:
_x在模块其他地方都是只读使用,一旦设置完成就不会再修改- 我知道可以把
_x封装到类里,让线程在栈上创建实例来实现线程安全,但现在基于类的方案不可行(比如模块是单例模式,set_x()的执行成本很高,我希望尽可能少地触发这个初始化操作)
答案:这种实现并不线程安全
问题出在哪?
这是典型的「检查-然后-执行」竞态条件:
- 假设有两个线程几乎同时进入
worker(),它们都会检测到_x is None - 结果就是两个线程都会调用
set_x()——哪怕最终_x的值是一样的,但你原本希望这个高成本的初始化只执行一次,现在却被重复触发了 - 退一步说,就算
set_x()只是简单的赋值(Python的全局变量赋值是原子操作),重复执行也会浪费资源;如果set_x()里还有其他非原子的逻辑(比如读取配置文件、调用外部接口),那还可能引发更复杂的线程安全问题
怎么修复?
给初始化过程加个锁就行,用Python的threading.Lock就能解决。这里推荐双重检查锁定的写法,既能保证线程安全,又能避免每次调用worker()都加锁的性能损耗:
修改后的代码示例:
import threading _x = None # 初始化锁,模块级别的锁所有线程共享 _x_init_lock = threading.Lock() def set_x(): global _x _x = # 你的初始化值(始终不变) def worker(): global _x # 第一次检查:如果已经初始化过,直接跳过锁逻辑 if _x is None: with _x_init_lock: # 第二次检查:等待锁的过程中,可能其他线程已经完成了初始化 if _x is None: set_x()
这种写法的好处是:只有第一次初始化的时候会用到锁,之后_x已经有值,所有线程都会直接跳过锁的逻辑,性能影响极小。
额外小提示
- 如果你的Python版本是3.9+,其实
functools.lru_cache(maxsize=None)可以把set_x()变成一个只执行一次的函数,但要注意如果set_x()有副作用,这种方式的线程安全依赖Python版本;相比之下,手动加锁的方式更透明、更稳妥 - 模块级变量是进程内共享的,所以这里用
threading.Lock足够;如果是多进程场景,那得换成multiprocessing.Lock,不过你这里是多线程场景,不用考虑这个
内容的提问来源于stack exchange,提问作者John Copella
相关产品推荐
相关产品推荐

