swift - 双重检查锁优化以在 Swift 中实现线程安全的延迟加载

标签 swift multithreading lazy-loading swift4 double-checked-locking

我已经在一个类中实现了我认为是双重检查锁定以实现线程安全的延迟加载。

以防万一你想知道,这是一个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,因此对instance 的访问可以从不同的线程并行运行。为了避免竞争条件(这是我不是 100% 确定的部分),我有两个障碍。一种是设置isSet,另一种是设置instance。诀窍是仅在 isSet 设置为 true 后才解锁后者,因此等待 instanceLocker 解锁的线程会在 isSet< 上第二次锁定 当它被异步写入并发调度队列时。

我认为我离这里的最终解决方案非常近,但由于我不是分布式系统专家,所以我想确定一下。

此外,使用调度队列不是我的第一选择,因为它让我觉得阅读 isSet 不是非常高效,但同样,我不是专家。

所以我的两个问题是:

  • 这是 100% 线程安全的吗?如果不是,为什么?
  • 有没有更高效的 在 Swift 中如何做到这一点?

最佳答案

IMO,此处正确的工具是 os_unfair_lock。双重检查锁定的要点是避免完全内核锁定的代价。 os_unfair_lock 在无争议的情况下提供。它的“不公平”部分是它不对等待线程做出 promise 。如果一个线程解锁,则允许它重新锁定而另一个等待线程没有机会(因此可能会饿死)。实际上,对于一个非常小的关键部分,这是不相关的(在这种情况下,您只是检查一个局部变量是否为 nil)。它是比分派(dispatch)到队列更低级别的原语,队列速度非常快,但不如不公平锁快,因为它依赖于像不公平锁这样的原语。

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 未 promise 的事情(包括编写 Bool 的原子性和它实际上会检查 bool 值两次,因为没有 volatile)。鉴于您正在进行同步,比较是在 os_unfair_lock 和调度到队列之间。

也就是说,根据我的经验,这种懒惰在移动应用程序中几乎总是没有根据的。如果变量非常昂贵但可能从未访问过,它实际上只会节省您的时间。有时在大规模并行系统中,能够移动初始化是值得的,但移动应用程序存在于相当有限的内核上,因此通常没有一些额外的内核可以将其分流到。我通常不会继续这样做,除非您已经发现当您的框架用于实时系统时这是一个严重的问题。如果你有,那么我建议在显示此问题的实际用法中针对 os_unfair_lock 分析你的方法。我希望 os_unfair_lock 获胜。

关于swift - 双重检查锁优化以在 Swift 中实现线程安全的延迟加载,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50779700/

相关文章:

ios - 使用 Eureka 表单隐藏部分的页眉和页脚

terminology - 延迟加载和延迟评估有什么区别?

jquery - 在水平 slider 中延迟加载图像

ios - 在 Swift 中显示以前创建的 View Controller

ios - popViewController(动画 : true) animation works slow

c++ - 调用 sf::Window::close 后 SFML 中的段错误

java - 使用java执行shell命令

Java:懒加载单例和反射攻击?

swift - 如何防止在 Swift 中重复实例化对象(通过其唯一 ID)?

java - 线程中断替代方案?