@property 在多线程下不安全,因其无同步机制,易引发竞态条件;需用实例级 threading.Lock、cached_property 或线程安全描述符解决。

为什么直接用 @property 在多线程下会出问题?
因为 @property 本身不带同步机制,当多个线程同时访问同一个实例的属性(尤其是该属性涉及懒加载或状态计算)时,可能触发多次初始化、覆盖中间状态,甚至引发 AttributeError 或数据错乱。典型场景如:单例式缓存、连接池管理器、配置加载器。
用 threading.Lock 包裹 getter 是最直接的解法
核心思路是:在属性访问入口加锁,确保同一时间只有一个线程能执行 getter 逻辑。注意锁对象必须绑定到实例(而非类),否则会阻塞所有实例——这常被忽略。
- 定义装饰器时,为每个实例动态生成独立的
_lock属性,例如在__init__中:self._lock = threading.Lock() - 装饰器内部用
with instance._lock:包裹原始 getter 的调用 - 避免在 getter 中做耗时操作(如网络请求),否则会严重拖慢并发性能
- 不要把锁对象设为类变量(如
cls._lock),那会让所有实例串行访问
更稳妥的做法:用 functools.cached_property(Python 3.8+)
它原生支持线程安全的懒加载,底层用了细粒度锁 + 原子性赋值(object.__setattr__),比手写锁更可靠。
- 仅适用于只读属性;一旦设置就不可修改
- 不能用于需要传参的动态计算(比如
@property带参数不行,cached_property同样不支持) - 若需兼容旧版本 Python,可用
backports.cached-property库替代 - 注意:它缓存的是实例级结果,不是类级——这点和手写锁方案一致
遇到 Race condition 但不想改属性访问方式?试试 __get__ 自定义描述符
把线程安全逻辑下沉到描述符层级,复用性更高,也更容易单元测试。
立即学习“Python免费学习笔记(深入)”;
- 实现一个继承
object的描述符类,重写__get__方法,在其中加锁并调用原始函数 - 锁对象建议存在 descriptor 实例里(
self._lock = threading.RLock()),避免跨实例干扰 - 用
RLock而非Lock,防止同一线程内递归访问导致死锁(比如 getter 内又访问了另一个带同样 descriptor 的属性) - 描述符中需显式处理
instance is None(访问类属性时),否则会报错


















