直接用类变量实现单例在多线程下会出错,因两个线程可能同时通过if _instance is none检查并各自创建实例;需用类级别threading.lock在__new__中加锁双重检查,且__init__需防重入,装饰器方式须注意锁作用域和继承问题。

为什么直接用类变量实现单例在多线程下会出错
直接写 _instance = None + if _instance is None 判断,在并发场景下可能创建多个实例。两个线程几乎同时通过 if 检查,都发现 _instance 是 None,接着各自执行构造逻辑,结果单例被破坏。
用 threading.Lock 保证初始化原子性
最稳妥的方式是在创建实例时加锁,确保只有一个线程能进入初始化分支。注意锁对象本身必须是类级别的,且要在类定义时就初始化,不能放在方法里重新创建。
-
__new__方法中先尝试获取锁,再检查并创建实例 - 锁对象(如
_lock)应定义在类体顶层,避免每次调用都新建一个锁 - 务必在
try/finally中释放锁,防止异常导致死锁
import threading
class Singleton:
_instance = None
_lock = threading.Lock()
def __new__(cls):
if cls._instance is None:
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
__init__ 被多次调用?那是你没控制好
即使 __new__ 返回了已有实例,Python 仍会无条件调用 __init__。如果你的初始化逻辑有副作用(比如打开文件、注册回调),就会重复执行。
快速生成专业的 Python 脚本和应用代码。一键创建完整项目结构,支持CLI、API、爬虫、Bot、Django等多种项目类型,包含完整的项目结构、配置文件、依赖管理、测试、README和文档。
- 常见误操作:把资源初始化写在
__init__里,又没做防护 - 正确做法:在
__init__开头加标记判断,或把初始化逻辑全移到__new__中(更干净) - 也可以用
_initialized实例属性做守卫,但需配合__new__中的设置
装饰器方式简洁但要注意作用域陷阱
用装饰器实现单例时,装饰器返回的闭包函数默认不感知线程安全,必须手动加锁,且锁要绑定到被装饰类而非装饰器闭包。
- 错误示范:锁定义在装饰器内部函数里 → 每次调用都新建锁,无效
- 正确做法:锁作为装饰器外层变量,或让被装饰类自己管理锁
- 装饰器对继承不友好,子类可能绕过单例约束,除非显式禁止继承
真正需要复用逻辑时,建议优先用元类或显式 __new__,比装饰器更可控。
锁粒度、__init__ 重入、装饰器作用域——这三个点漏掉任何一个,线程安全单例就只是看起来安全。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










