【问题标题】:What is the Swift equivalent to Objective-C's "@synchronized"?什么是 Swift 等价于 Objective-C 的“@synchronized”?
【发布时间】:2014-07-25 14:27:52
【问题描述】:

我搜索了 Swift 书籍,但找不到 @synchronized 的 Swift 版本。如何在 Swift 中进行互斥?

【问题讨论】:

  • 我会使用调度屏障。屏障提供了非常便宜的同步。 dispatch_barrier_async()。等
  • @FrederickC.Lee,如果您需要同步 write 怎么办,例如为removeFirst() 创建包装器时?

标签: concurrency mutex swift


【解决方案1】:

怎么样

final class SpinLock {
    private let lock = NSRecursiveLock()

    func sync<T>(action: () -> T) -> T {
        lock.lock()
        defer { lock.unlock() }
        return action()
    }
}

【讨论】:

    【解决方案2】:

    试试:NSRecursiveLock

    同一个线程可以多次获取的锁 导致死锁。

    let lock = NSRecursiveLock()
    
    func f() {
        lock.lock()
        //Your Code
        lock.unlock()
    }
    
    func f2() {
        lock.lock()
        defer {
            lock.unlock()
        }
        //Your Code
    }
    

    Objective-C 同步功能支持递归和 可重入代码。一个线程可以在一个线程中多次使用一个信号量 递归方式;其他线程被阻止使用它,直到 线程释放所有用它获得的锁;也就是说,每 @synchronized() 块正常退出或通过异常退出。 Source

    【讨论】:

      【解决方案3】:

      详情

      Xcode 8.3.1、Swift 3.1

      任务

      从不同线程读取写入值(异步)。

      代码

      class AsyncObject<T>:CustomStringConvertible {
          private var _value: T
          public private(set) var dispatchQueueName: String
         
          let dispatchQueue: DispatchQueue
          
          init (value: T, dispatchQueueName: String) {
              _value = value
              self.dispatchQueueName = dispatchQueueName
              dispatchQueue = DispatchQueue(label: dispatchQueueName)
          }
          
          func setValue(with closure: @escaping (_ currentValue: T)->(T) ) {
              dispatchQueue.sync { [weak self] in
                  if let _self = self {
                      _self._value = closure(_self._value)
                  }
              }
          }
          
          func getValue(with closure: @escaping (_ currentValue: T)->() ) {
              dispatchQueue.sync { [weak self] in
                  if let _self = self {
                      closure(_self._value)
                  }
              }
          }
          
          
          var value: T {
              get {
                  return dispatchQueue.sync { _value }
              }
              
              set (newValue) {
                  dispatchQueue.sync { _value = newValue }
              }
          }
      
          var description: String {
              return "\(_value)"
          }
      }
      

      用法

      print("Single read/write action")
      // Use it when when you need to make single action
      let obj = AsyncObject<Int>(value: 0, dispatchQueueName: "Dispatch0")
      obj.value = 100
      let x = obj.value
      print(x)
      
      print("Write action in block")
      // Use it when when you need to make many action
      obj.setValue{ (current) -> (Int) in
          let newValue = current*2
          print("previous: \(current), new: \(newValue)")
          return newValue
      }
      

      完整样本

      扩展调度组

      extension DispatchGroup {
          
          class func loop(repeatNumber: Int, action: @escaping (_ index: Int)->(), completion: @escaping ()->()) {
              let group = DispatchGroup()
              for index in 0...repeatNumber {
                  group.enter()
                  DispatchQueue.global(qos: .utility).async {
                      action(index)
                      group.leave()
                  }
              }
              
              group.notify(queue: DispatchQueue.global(qos: .userInitiated)) {
                  completion()
              }
          }
      }
      

      类视图控制器

      import UIKit
      
      class ViewController: UIViewController {
      
          override func viewDidLoad() {
              super.viewDidLoad()
      
              //sample1()
              sample2()
          }
          
          func sample1() {
              print("=================================================\nsample with variable")
              
              let obj = AsyncObject<Int>(value: 0, dispatchQueueName: "Dispatch1")
              
              DispatchGroup.loop(repeatNumber: 5, action: { index in
                  obj.value = index
              }) {
                  print("\(obj.value)")
              }
          }
          
          func sample2() {
              print("\n=================================================\nsample with array")
              let arr = AsyncObject<[Int]>(value: [], dispatchQueueName: "Dispatch2")
              DispatchGroup.loop(repeatNumber: 15, action: { index in
                  arr.setValue{ (current) -> ([Int]) in
                      var array = current
                      array.append(index*index)
                      print("index: \(index), value \(array[array.count-1])")
                      return array
                  }
              }) {
                  print("\(arr.value)")
              }
          }
      }
      

      【讨论】:

        【解决方案4】:

        综上,这里给出更常见的方式,包括返回值或void,以及抛出

        import Foundation
        
        extension NSObject {
        
        
            func synchronized<T>(lockObj: AnyObject!, closure: () throws -> T) rethrows ->  T
            {
                objc_sync_enter(lockObj)
                defer {
                    objc_sync_exit(lockObj)
                }
        
                return try closure()
            }
        
        
        }
        

        【讨论】:

        • 为什么defer {sync_exit}sync_enter 之后而不是之前?在开发会议中,我听说 defer 应该放在函数内的所有代码之前:)
        • 因为合理地使 objc_sync_exit 必须发生在 objc_sync_enter 之后。
        • 但是如果你把它放在之前它是在进入后退出,它是退出范围时退出,对吗?)
        【解决方案5】:

        使用 Swift 的属性包装器,这就是我现在使用的:

        @propertyWrapper public struct NCCSerialized<Wrapped> {
            private let queue = DispatchQueue(label: "com.nuclearcyborg.NCCSerialized_\(UUID().uuidString)")
        
            private var _wrappedValue: Wrapped
            public var wrappedValue: Wrapped {
                get { queue.sync { _wrappedValue } }
                set { queue.sync { _wrappedValue = newValue } }
            }
        
            public init(wrappedValue: Wrapped) {
                self._wrappedValue = wrappedValue
            }
        }
        

        那么你可以这样做:

        @NCCSerialized var foo: Int = 10
        

        @NCCSerialized var myData: [SomeStruct] = []
        

        然后像往常一样访问变量。

        【讨论】:

        • 我喜欢这个解决方案,但对@Decorating 的人的成本感到好奇,因为这样做的副作用是创建一个对用户隐藏的DispatchQueue。我发现这个 SO 参考让我放心:stackoverflow.com/a/35022486/1060314
        • 属性包装器本身很轻——只是一个结构,所以,它是你能做的最轻的东西之一。不过感谢 DispatchQueue 上的链接。我一直在考虑对 queue.sync wrap 与其他解决方案(以及与无队列)进行一些性能测试,但没有这样做。
        【解决方案6】:

        在现代 Swift 5 中,具有返回功能:

        /**
        Makes sure no other thread reenters the closure before the one running has not returned
        */
        @discardableResult
        public func synchronized<T>(_ lock: AnyObject, closure:() -> T) -> T {
            objc_sync_enter(lock)
            defer { objc_sync_exit(lock) }
        
            return closure()
        }
        

        像这样使用它,以利用返回值功能:

        let returnedValue = synchronized(self) { 
             // Your code here
             return yourCode()
        }
        

        或者这样:

        synchronized(self) { 
             // Your code here
            yourCode()
        }
        

        【讨论】:

        • 这是正确的答案,而不是被接受和高度赞成的答案(取决于GCD)。似乎基本上没有人使用或了解如何使用Thread。我对此很满意 - 而GCD 充满了陷阱和限制。
        • 正确答案需要使用递归锁,objc_sync_enter也是如此。我更喜欢将 lock 参数隐藏在私有 let 或 iVar 中,而不是使用 self,除非它需要发布以允许其他人也同步。这是一个非常罕见的情况,但如果发生这种情况,使用 objc_sync_enter 允许 swift 和 Objective-C 之间的合作。这个答案还允许返回一个值。由于这些原因,我选择了这个答案用于我的项目。
        【解决方案7】:

        在 2018 年 WWDC 的“了解崩溃和崩溃日志”session 414 中,他们展示了以下使用带同步的 DispatchQueues 的方式。

        在 swift 4 中应该是这样的:

        class ImageCache {
            private let queue = DispatchQueue(label: "sync queue")
            private var storage: [String: UIImage] = [:]
            public subscript(key: String) -> UIImage? {
                get {
                  return queue.sync {
                    return storage[key]
                  }
                }
                set {
                  queue.sync {
                    storage[key] = newValue
                  }
                }
            }
        }
        

        无论如何,您也可以使用带屏障的并发队列来加快读取速度。同步和异步读取是同时执行的,写入新值会等待先前的操作完成。

        class ImageCache {
            private let queue = DispatchQueue(label: "with barriers", attributes: .concurrent)
            private var storage: [String: UIImage] = [:]
        
            func get(_ key: String) -> UIImage? {
                return queue.sync { [weak self] in
                    guard let self = self else { return nil }
                    return self.storage[key]
                }
            }
        
            func set(_ image: UIImage, for key: String) {
                queue.async(flags: .barrier) { [weak self] in
                    guard let self = self else { return }
                    self.storage[key] = image
                }
            }
        }
        

        【讨论】:

        • 您可能不需要使用同步来阻止读取和减慢队列速度。您可以只使用同步进行串行写入。
        【解决方案8】:

        图我将发布基于先前答案的 Swift 5 实现。多谢你们!我发现有一个返回值也很有帮助,所以我有两种方法。

        这是一个简单的类:

        import Foundation
        class Sync {
        public class func synced(_ lock: Any, closure: () -> ()) {
                objc_sync_enter(lock)
                defer { objc_sync_exit(lock) }
                closure()
            }
            public class func syncedReturn(_ lock: Any, closure: () -> (Any?)) -> Any? {
                objc_sync_enter(lock)
                defer { objc_sync_exit(lock) }
                return closure()
            }
        }
        

        如果需要返回值,则像这样使用它:

        return Sync.syncedReturn(self, closure: {
            // some code here
            return "hello world"
        })
        

        或者:

        Sync.synced(self, closure: {
            // do some work synchronously
        })
        

        【讨论】:

        • 试试public class func synced&lt;T&gt;(_ lock: Any, closure: () -&gt; T),适用于 void 和任何其他类型。还有再生的东西。
        • @hnh 重新生长的东西是什么意思?此外,如果您愿意分享一个对具有类型 的泛型方法的示例调用,这将帮助我更新答案 - 我喜欢您将要使用的地方。
        • 重新抛出,而不是重新增长,srz
        【解决方案9】:

        您可以使用 GCD。它比@synchronized 稍微冗长一些,但可以作为替代:

        let serialQueue = DispatchQueue(label: "com.test.mySerialQueue")
        serialQueue.sync {
            // code
        }
        

        【讨论】:

        • 这很好,但是缺少 @synchronized 的重新进入能力。
        • 使用这种方法你需要小心。您的块可能在其他线程上执行。 API 文档说:“作为一种优化,此函数会在可能的情况下调用当前线程上的块。”
        • 不,不,不。不错的尝试,但效果并不理想。为什么?基本阅读(替代方案的综合比较、注意事项)和来自 Matt Gallagher 的出色实用程序框架,在这里:cocoawithlove.com/blog/2016/06/02/threads-and-mutexes.html@wuf810 第一次提到了这个(HT),但低估了这篇文章的好坏。都应该读。 (请至少投票以使其最初可见,但仅此而已。)
        • 有人可以澄清为什么这个答案会导致死锁吗? Matt Gallagher 的文章清楚地说明了为什么这会比@synchronized 慢,但为什么会导致死锁呢? @TomKraina @bio @t0rst
        【解决方案10】:

        SWIFT 4

        在 Swift 4 中,您可以使用 GCD 调度队列来锁定资源。

        class MyObject {
            private var internalState: Int = 0
            private let internalQueue: DispatchQueue = DispatchQueue(label:"LockingQueue") // Serial by default
        
            var state: Int {
                get {
                    return internalQueue.sync { internalState }
                }
        
                set (newState) {
                    internalQueue.sync { internalState = newState }
                }
            }
        } 
        

        【讨论】:

        • 这似乎不适用于 XCode8.1。 .serial 似乎不可用。但是.concurrent 可用。 ://
        • 默认是.serial
        • 请注意,此模式无法正确防范最常见的多线程问题。例如,如果您同时运行myObject.state = myObject.state + 1,它不会计算总操作数,而是产生一个不确定的值。为了解决这个问题,调用代码应该包装在一个串行队列中,以便读取和写入都原子地发生。当然 Obj-c 的@synchronised 也有同样的问题,所以从这个意义上说你的实现是正确的。
        • 是的,myObject.state += 1 是读操作和写操作的组合。其他一些线程仍然可以介于两者之间来设置/写入一个值。根据objc.io/blog/2018/12/18/atomic-variables,在同步块/闭包中而不是在变量本身下运行set 会更容易。
        【解决方案11】:

        在 Swift4 中使用 NSLock

        let lock = NSLock()
        lock.lock()
        if isRunning == true {
                print("Service IS running ==> please wait")
                return
        } else {
            print("Service not running")
        }
        isRunning = true
        lock.unlock()
        

        警告 NSLock 类使用 POSIX 线程来实现其锁定行为。当向 NSLock 对象发送解锁消息时,您必须确保该消息是从发送初始锁定消息的同一线程发送的。从不同的线程解锁锁可能会导致未定义的行为。

        【讨论】:

        【解决方案12】:

        我自己也在寻找这个,并得出结论,在 swift 中还没有原生结构。

        我确实根据我从 Matt Bridges 和其他人那里看到的一些代码编写了这个小辅助函数。

        func synced(_ lock: Any, closure: () -> ()) {
            objc_sync_enter(lock)
            closure()
            objc_sync_exit(lock)
        }
        

        使用非常简单

        synced(self) {
            println("This is a synchronized closure")
        }
        

        我发现了一个问题。在这一点上,将数组作为锁定参数传递似乎会导致一个非常迟钝的编译器错误。否则,尽管它似乎可以按预期工作。

        Bitcast requires both operands to be pointer or neither
          %26 = bitcast i64 %25 to %objc_object*, !dbg !378
        LLVM ERROR: Broken function found, compilation aborted!
        

        【讨论】:

        • 这非常有用,并且很好地保留了 @synchronized 块的语法,但请注意,它与真正的内置块语句不同,例如 Objective-C 中的 @synchronized 块,因为 @ 987654327@ 和 break 语句不再像普通语句那样跳出周围的函数/循环。
        • 错误可能是由于数组作为值而不是引用传递
        • 这可能是使用新的defer 关键字确保objc_sync_exit 被调用的好地方,即使closure 抛出。
        • @t0rst 根据链接到的文章将此答案称为“有缺陷”是无效的。文章称这种方法“比理想的慢一点”并且“仅限于 Apple 平台”。但这并不会使其长期存在“缺陷”。
        • 这篇非常有趣的文章解释了objc_sync_xxx的一个陷阱:straypixels.net/swift-dictionary-locking
        【解决方案13】:

        Objective-C 中的 @synchronized 指令的模拟可以具有任意返回类型和良好的 rethrows 在 Swift 中的行为。

        // Swift 3
        func synchronized<T>(_ lock: AnyObject, _ body: () throws -> T) rethrows -> T {
            objc_sync_enter(lock)
            defer { objc_sync_exit(lock) }
            return try body()
        }
        

        使用defer 语句可以直接返回一个值,而无需引入临时变量。


        在 Swift 2 中,将 @noescape 属性添加到闭包以允许更多优化:

        // Swift 2
        func synchronized<T>(lock: AnyObject, @noescape _ body: () throws -> T) rethrows -> T {
            objc_sync_enter(lock)
            defer { objc_sync_exit(lock) }
            return try body()
        }
        

        基于 GNewc [1](我喜欢任意返回类型)和 Tod Cunningham [2](我喜欢 defer)的答案。

        【讨论】:

        • Xcode 告诉我 @noescape 现在是默认设置,在 Swift 3 中已弃用。
        • 没错,这个答案中的代码是针对 Swift 2 的,需要对 Swift 3 进行一些调整。有时间我会更新它。
        • 你能解释一下用法吗?也许有一个例子..提前谢谢!就我而言,我有一个需要同步的 Set,因为我在 DispatchQueue 中操作它的内容。
        • @sancho 我希望这篇文章保持简洁。您似乎在询问一般并发编程指南,这是一个广泛的问题。尝试将其作为一个单独的问题提出!
        【解决方案14】:

        斯威夫特 3

        此代码具有重入能力,可以与异步函数调用一起使用。在此代码中,调用 someAsyncFunc() 后,串行队列上的另一个函数闭包将处理但被 semaphore.wait() 阻塞,直到调用 signal()。不应该使用 internalQueue.sync ,因为如果我没记错的话它会阻塞主线程。

        let internalQueue = DispatchQueue(label: "serialQueue")
        let semaphore = DispatchSemaphore(value: 1)
        
        internalQueue.async {
        
            self.semaphore.wait()
        
            // Critical section
        
            someAsyncFunc() {
        
                // Do some work here
        
                self.semaphore.signal()
            }
        }
        

        objc_sync_enter/objc_sync_exit 如果没有错误处理就不是一个好主意。

        【讨论】:

        • 什么错误处理?编译器不允许任何抛出的东西。另一方面,如果不使用 objc_sync_enter/exit,就会放弃一些实质性的性能提升。
        【解决方案15】:

        使用 Bryan McLemore 的答案,我将其扩展为支持使用 Swift 2.0 延迟能力投掷到安全庄园的对象。

        func synchronized( lock:AnyObject, block:() throws -> Void ) rethrows
        {
            objc_sync_enter(lock)
            defer {
                objc_sync_exit(lock)
            }
        
            try block()
        }
        

        【讨论】:

        • 最好使用rethrows来简化使用非抛出闭包(无需使用try),如my answer所示。
        【解决方案16】:

        dispatch_barrier_async 是更好的方法,同时不会阻塞当前线程。

        dispatch_barrier_async(accessQueue, { 字典[object.ID] = 对象 })

        【讨论】:

          【解决方案17】:

          Based on ɲeuroburɳ,测试一个子类案例

          class Foo: NSObject {
              func test() {
                  print("1")
                  objc_sync_enter(self)
                  defer {
                      objc_sync_exit(self)
                      print("3")
                  }
          
                  print("2")
              }
          }
          
          
          class Foo2: Foo {
              override func test() {
                  super.test()
          
                  print("11")
                  objc_sync_enter(self)
                  defer {
                      print("33")
                      objc_sync_exit(self)
                  }
          
                  print("22")
              }
          }
          
          let test = Foo2()
          test.test()
          

          输出:

          1
          2
          3
          11
          22
          33
          

          【讨论】:

            【解决方案18】:

            我喜欢并使用这里的许多答案,所以我会选择最适合你的答案。也就是说,当我需要诸如objective-c的@synchronized之类的东西时,我更喜欢使用swift 2中引入的defer语句。

            { 
                objc_sync_enter(lock)
                defer { objc_sync_exit(lock) }
            
                //
                // code of critical section goes here
                //
            
            } // <-- lock released when this block is exited
            

            这种方法的好处是,您的临界区可以以任何所需的方式退出包含块(例如,returnbreakcontinuethrow),并且“在无论程序控制如何转移,defer 语句都会执行。"1

            【讨论】:

            • 我认为这可能是这里提供的最优雅的解决方案。感谢您的反馈。
            • 什么是locklock是如何初始化的?
            • lock 是任何objective-c 对象。
            • 太棒了!当 Swift 1 被引入时,我已经编写了一些锁定辅助方法,并且有一段时间没有重新访问这些方法。完全忘记了延迟;这是要走的路!
            • 很好的答案。可以说可以使用 do { ... } 来定义代码块,所以do { obj_sync_enter(lock); defer { obj_sync_exit(lock); }; ...code... } 实现了与@synchronized{ ...code... } 相同的效果
            【解决方案19】:

            为什么要让锁变得困难和麻烦? 使用调度障碍。

            调度屏障在并发队列中创建一个同步点。

            当它运行时,不允许队列上的其他块运行,即使它是并发的并且其他内核可用。

            如果这听起来像是一个排他(写)锁,那就是。 非屏障块可以被认为是共享(读)锁。

            只要对资源的所有访问都是通过队列执行的,屏障就可以提供非常便宜的同步。

            【讨论】:

            • 我的意思是,您假设使用 GCD 队列来同步访问,但这在原始问题中没有提到。并且只有并发队列才需要屏障 - 您可以简单地使用串行队列将相互排除的块排队以模拟锁。
            • 我的问题,为什么要模拟锁?根据我的阅读,由于开销与队列中的障碍相比,不鼓励使用锁。
            【解决方案20】:

            另一种方法是创建一个超类,然后继承它。这样你可以更直接地使用GCD

            class Lockable {
                let lockableQ:dispatch_queue_t
            
                init() {
                    lockableQ = dispatch_queue_create("com.blah.blah.\(self.dynamicType)", DISPATCH_QUEUE_SERIAL)
                }
            
                func lock(closure: () -> ()) {
                    dispatch_sync(lockableQ, closure)
                }
            }
            
            
            class Foo: Lockable {
            
                func boo() {
                    lock {
                        ....... do something
                    }
                }
            

            【讨论】:

            • -1 继承为您提供子类型多态性以换取增加耦合。如果您不需要前者,请避免使用后者。不要偷懒。更喜欢组合代码重用。
            【解决方案21】:

            要添加返回功能,您可以这样做:

            func synchronize<T>(lockObj: AnyObject!, closure: ()->T) -> T
            {
              objc_sync_enter(lockObj)
              var retVal: T = closure()
              objc_sync_exit(lockObj)
              return retVal
            }
            

            随后,您可以使用以下方式调用它:

            func importantMethod(...) -> Bool {
              return synchronize(self) {
                if(feelLikeReturningTrue) { return true }
                // do other things
                if(feelLikeReturningTrueNow) { return true }
                // more things
                return whatIFeelLike ? true : false
              }
            }
            

            【讨论】:

              【解决方案22】:

              您可以将语句夹在objc_sync_enter(obj: AnyObject?)objc_sync_exit(obj: AnyObject?) 之间。 @synchronized 关键字在幕后使用了这些方法。即

              objc_sync_enter(self)
              ... synchronized code ...
              objc_sync_exit(self)
              

              【讨论】:

              • 这会被认为是 Apple 使用私有 API 吗?
              • 不,objc_sync_enterobjc_sync_exit 是在 Objc-sync.h 中定义的方法并且是开源的:opensource.apple.com/source/objc4/objc4-371.2/runtime/…
              • 如果多个线程尝试访问同一个资源会发生什么情况,第二个是等待、重试还是崩溃?
              • 除了@bontoJR 所说的,objc_sync_enter(…)objc_sync_exit(…) 是 iOS/macOS/etc 提供的公共标头。 API (看起来它们位于路径 usr/include/objc/objc-sync.h….sdk 内)。找出某个东西是否是公共 API 的最简单方法是(在 Xcode 中) 键入函数名称 (例如 objc_sync_enter();不需要为 C 指定参数函数),然后尝试命令单击它。如果它向您显示该 API 的头文件,那么您很好 (因为如果它不公开,您将无法看到头文件).
              猜你喜欢
              • 2014-07-28
              • 2023-03-18
              • 1970-01-01
              • 2017-11-09
              • 2017-10-29
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多