You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在属性的willSet与didSet中使用NSLock保护多线程写入是否可行?

在属性willSet/didSet中用NSLock保护多线程写入是否可行?

我想了解在属性的willSet和didSet中使用NSLock来保护多线程环境下的写入操作是否为可行的解决方案。示例代码如下:

private let lock = NSLock() 
private static var cachedUserTokenDetails: UserTokenDetails? { 
    willSet { lock.lock() } 
    didSet { lock.unlock() } 
} 
class func getUserTokenDetails() -> UserTokenDetails? { 
    if cachedUserTokenDetails == nil { 
        cachedUserTokenDetails = getUserTokenDetailsFromKeychain() 
    } 
    return cachedUserTokenDetails 
}

我认为这种方式是安全的,因为willSet和didSet总会被调用,请问是否正确?


咱们直接说结论:你这个实现并不安全,问题出在几个关键细节上——willSet/didSet确实总会执行,但你的锁用法本身就存在核心问题:

1. 锁的作用域完全不匹配

你定义的lock是实例属性,但cachedUserTokenDetails是静态类属性。这意味着你的类每创建一个实例,就会生成一个独立的lock,而静态缓存属性是全局共享的。多线程环境下,不同线程可能通过不同实例的lock尝试保护同一个静态属性,这相当于没有锁——多个线程能同时进入赋值操作,完全起不到线程同步的作用。

2. 读取操作完全没加锁

你的getUserTokenDetails方法里,读取cachedUserTokenDetails判断是否为nil的过程没有任何锁保护。多线程场景下,一个线程正在执行赋值操作(持有某个lock),另一个线程可能同时在读取这个属性,这会导致读取到不完整的中间状态(比如属性内存正在被写入但还没完成),这也是典型的线程不安全场景。

3. 锁的覆盖范围不对

即使锁的作用域没问题,你把lock放在willSet和didSet里,也只能覆盖赋值前后的瞬间,而整个赋值操作(包括属性的内存写入)是在willSet和didSet之间执行的。但更关键的是,读取操作完全在锁的保护之外,这根本无法保证多线程下的读写安全。


正确的实现方式

要解决这些问题,我们需要做到两点:让锁和静态属性同作用域,并且所有读写操作都在锁的保护下。这里给你两种可行的写法:

写法一:直接在方法里加锁(更简洁)

private static let lock = NSLock()
private static var _cachedUserTokenDetails: UserTokenDetails? // 私有存储属性

class func getUserTokenDetails() -> UserTokenDetails? {
    lock.lock()
    defer { lock.unlock() } // 用defer确保锁一定会释放,哪怕后面代码抛出异常
    
    if _cachedUserTokenDetails == nil {
        _cachedUserTokenDetails = getUserTokenDetailsFromKeychain()
    }
    return _cachedUserTokenDetails
}

写法二:用计算属性封装锁(更符合属性封装的习惯)

private static let lock = NSLock()
private static var _cachedUserTokenDetails: UserTokenDetails?

private static var cachedUserTokenDetails: UserTokenDetails? {
    get {
        lock.lock()
        defer { lock.unlock() }
        return _cachedUserTokenDetails
    }
    set {
        lock.lock()
        defer { lock.unlock() }
        _cachedUserTokenDetails = newValue
    }
}

class func getUserTokenDetails() -> UserTokenDetails? {
    if cachedUserTokenDetails == nil {
        cachedUserTokenDetails = getUserTokenDetailsFromKeychain()
    }
    return cachedUserTokenDetails
}

这两种写法都能保证所有对缓存属性的读写操作都在同一个静态lock的保护下,真正实现多线程安全。

内容的提问来源于stack exchange,提问作者ricardopereira

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 04:20:43