我已经实现了我认为在类中进行双重检查锁定以实现线程安全的延迟加载.
万一你想知道,这是我目前正在努力的DI library.
我正在谈论的代码是the following:
final class Builder<I> { private let body: () -> I private var instance: I? private let instanceLocker = NSLock() private var isSet = false private let isSetdispatchQueue = dispatchQueue(label: "\(Builder.self)",attributes: .concurrent) init(body: @escaping () -> I) { self.body = body } private var syncIsSet: Bool { set { isSetdispatchQueue.async(flags: .barrier) { self.isSet = newValue } } get { var isSet = false isSetdispatchQueue.sync { isSet = self.isSet } return isSet } } var value: I { if syncIsSet { return instance! // should never fail } instanceLocker.lock() if syncIsSet { instanceLocker.unlock() return instance! // should never fail } let instance = body() self.instance = instance syncIsSet = true instanceLocker.unlock() return instance } }
逻辑是允许并发读取isSet,因此对实例的访问可以从不同的线程并行运行.为了避免竞争条件(这是我不能100%肯定的部分),我有两个障碍.设置为Set时设置一个,设置实例时设置一个.诀窍是仅在isSet设置为true后解锁后者,因此等待onLocker解锁的线程在isSet上被异步写入并发调度队列时第二次被锁定.
我认为我离这里的最终解决方案非常接近,但由于我不是分布式系统专家,所以我想确定一下.
此外,使用调度队列不是我的第一选择,因为它让我觉得阅读isSet不是超级高效,但同样,我不是专家.
所以我的两个问题是:
>这是100%线程安全的,如果没有,为什么?
>有效率吗?
在Swift中这样做的方式?
解决方法
IMO,这里正确的工具是os_unfair_lock.双重检查锁定的目的是避免完全内核锁定的代价. os_unfair_lock在无争议的情况下提供.它的“不公平”部分是它不会对等待线程作出承诺.如果一个线程解锁,则允许重新锁定而没有另一个等待线程获得机会(因此可能会饿死).在实践中使用非常小的临界区,这是不相关的(在这种情况下,您只是检查局部变量与nil).它是一个比调度队列更低级的原语,它非常快,但不如unfair_lock快,因为它依赖于像unfair_lock这样的原语.
final class Builder<I> { private let body: () -> I private var lock = os_unfair_lock() init(body: @escaping () -> I) { self.body = body } private var _value: I! var value: I { os_unfair_lock_lock(&lock) if _value == nil { _value = body() } os_unfair_lock_unlock(&lock) return _value } }
请注意,在syncIsSet上执行同步是正确的.如果你把它当作一个原语(在其他的双重检查同步中很常见),那么你将依赖于Swift不承诺的东西(编写Bools的原子和它实际上会检查布尔值)两次,因为没有波动).鉴于您正在进行同步,比较是在os_unfair_lock和调度到队列之间进行的.
这说,根据我的经验,这种懒惰在移动应用程序中几乎总是没有根据的.如果变量非常昂贵,但可能永远不会访问,它实际上只会节省您的时间.有时在大规模并行系统中,能够移动初始化是值得的,但是移动应用程序存在于相当有限数量的核心上,因此通常没有一些额外的核心可以将其分流.我通常不会追求这个,除非你已经发现当你的框架在实时系统中使用时这是一个重大问题.如果有,那么我建议在显示此问题的实际用法中对os_unfair_lock进行分析.我希望os_unfair_lock获胜.