【问题标题】:Are Swift variables atomic?Swift 变量是原子的吗?
【发布时间】:2014-08-01 05:02:41
【问题描述】:

在 Objective-C 中,您可以区分原子属性和非原子属性:

@property (nonatomic, strong) NSObject *nonatomicObject;
@property (atomic, strong) NSObject *atomicObject;

据我了解,您可以安全地从多个线程读取和写入定义为原子的属性,而同时从多个线程写入和访问非原子属性或 ivars 可能会导致未定义的行为,包括错误的访问错误。

所以如果你在 Swift 中有这样的变量:

var object: NSObject

我可以安全地并行读写这个变量吗? (不考虑这样做的实际意义)。

【问题讨论】:

  • 我想在未来,也许我们可以使用@atomic@nonatomic。或者默认情况下只是原子的。 (Swift 太不完整了,我们现在不能说太多)
  • IMO,默认情况下他们会让所有东西都变成非原子的,并且可能会提供一个特殊的功能来制作原子的东西。
  • 顺便说一句,atomic 通常被认为不足以与属性进行线程安全交互,除了简单的数据类型。对于对象,通常使用锁(例如,NSLock@synchronized)或 GCD 队列(例如,具有“读写器”模式的串行队列或并发队列)跨线程同步访问。
  • @Rob,是的,尽管由于 Objective-C(可能在 Swift 中)中的引用计数,在没有原子访问的情况下并发读取和写入变量可能会导致内存损坏。如果所有变量都具有原子访问权限,那么可能发生的最糟糕的事情将是“逻辑”竞争条件,即意外行为。
  • 不要误会我的意思:我希望 Apple 回答/解决原子行为问题。只是(a)atomic 不能确保对象的线程安全; (b) 如果正确使用上述同步技术之一来确保线程安全(除其他外,防止同时读/写),那么原子问题就没有实际意义。但是对于简单的数据类型,我们仍然需要/想要它,atomic 具有真正的价值。好问题!

标签: objective-c swift


【解决方案1】:

现在假设没有可用的低级文档还为时过早,但您可以从汇编中学习。 Hopper Disassembler 是一个很棒的工具。

@interface ObjectiveCar : NSObject
@property (nonatomic, strong) id engine;
@property (atomic, strong) id driver;
@end

objc_storeStrongobjc_setProperty_atomic 分别用于非原子和原子,其中

class SwiftCar {
    var engine : AnyObject?    
    init() {
    }
}

使用来自libswift_stdlib_coreswift_retain,并且显然没有内置线程安全。

我们可以推测以后可能会引入额外的关键字(类似于@lazy)。

2015 年 7 月 20 日更新:根据这个 blogpost on singletons swift 环境可以使某些情况对您来说是线程安全的,即:

class Car {
    static let sharedCar: Car = Car() // will be called inside of dispatch_once
}

private let sharedCar: Car2 = Car2() // same here
class Car2 {

}

2016 年 5 月 25 日更新:留意 swift evolution 提议 https://github.com/apple/swift-evolution/blob/master/proposals/0030-property-behavior-decls.md - 看起来有可能由您自己实现 @atomic 行为。 p>

【讨论】:

  • 我用最近的一些信息更新了我的答案,希望对您有所帮助
  • 您好,感谢您提供 Hopper Disassembler 工具的链接。看起来很整洁。
【解决方案2】:

Swift 没有围绕线程安全的语言结构。假设您将使用提供的库来进行自己的线程安全管理。 在实现线程安全方面有很多选择,包括 pthread 互斥锁、NSLock 和 dispatch_sync 作为互斥锁机制。请参阅 Mike Ash 最近关于该主题的帖子:https://mikeash.com/pyblog/friday-qa-2015-02-06-locks-thread-safety-and-swift.html 所以直接回答你的问题“我可以安全地并行读写这个变量吗?”是没有。

【讨论】:

    【解决方案3】:

    现在回答这个问题可能为时过早。目前 swift 缺少访问修饰符,因此没有明显的方法来添加代码来管理围绕属性 getter/setter 的并发性。此外,Swift 语言似乎还没有任何关于并发的信息! (它也缺少 KVO 等...)

    我认为这个问题的答案将在未来的版本中变得清晰。

    【讨论】:

    • re:缺少 KVO,请查看 willSetdidSet - 似乎是第一步
    • willSet, didSet 更适用于总是需要自定义设置器的属性,因为它们必须做一些事情。例如一个颜色属性,当属性更改为不同的值时需要重绘一个视图;现在使用 didSet 可以更轻松地完成。
    • 是的,这就是我所说的“第一步”的意思:)我认为这可能是该功能可用但尚未完全实现的标志
    【解决方案4】:

    详情

    • Xcode 9.1,Swift 4
    • Xcode 10.2.1 (10E1001)、Swift 5

    链接

    实现的类型

    主要思想

    class Example {
        
        private lazy var semaphore = DispatchSemaphore(value: 1)
        
        func executeThreadSafeFunc1() {
            // Lock access. Only first thread can execute code below.
            // Other threads will wait until semaphore.signal() will execute
            semaphore.wait()
            // your code
            semaphore.signal()         // Unlock access
        }
        
        func executeThreadSafeFunc2() {
            // Lock access. Only first thread can execute code below.
            // Other threads will wait until semaphore.signal() will execute
            semaphore.wait()
            DispatchQueue.global(qos: .background).async {
                // your code
                self.semaphore.signal()         // Unlock access
            }
        }
    }
    

    原子访问示例

    class Atomic {
        
        let dispatchGroup = DispatchGroup()
        private var variable = 0
        
        // Usage of semaphores
        
        func semaphoreSample() {
            
            // value: 1 - number of threads that have simultaneous access to the variable
            let atomicSemaphore = DispatchSemaphore(value: 1)
            variable = 0
            
            runInSeveralQueues { dispatchQueue  in
                // Only (value) queqes can run operations betwen atomicSemaphore.wait() and atomicSemaphore.signal()
                // Others queues await their turn
                atomicSemaphore.wait()            // Lock access until atomicSemaphore.signal()
                self.variable += 1
                print("\(dispatchQueue), value: \(self.variable)")
                atomicSemaphore.signal()          // Unlock access
            }
            
            notifyWhenDone {
                atomicSemaphore.wait()           // Lock access until atomicSemaphore.signal()
                print("variable = \(self.variable)")
                atomicSemaphore.signal()         // Unlock access
            }
        }
        
        // Usage of sync of DispatchQueue
        
        func dispatchQueueSync() {
            let atomicQueue = DispatchQueue(label: "dispatchQueueSync")
            variable = 0
            
            runInSeveralQueues { dispatchQueue  in
                
                // Only queqe can run this closure (atomicQueue.sync {...})
                // Others queues await their turn
                atomicQueue.sync {
                    self.variable += 1
                    print("\(dispatchQueue), value: \(self.variable)")
                }
            }
            
            notifyWhenDone {
                atomicQueue.sync {
                    print("variable = \(self.variable)")
                }
            }
        }
        
        // Usage of objc_sync_enter/objc_sync_exit
        
        func objcSync() {
            variable = 0
            
            runInSeveralQueues { dispatchQueue  in
                
                // Only one queqe can run operations betwen objc_sync_enter(self) and objc_sync_exit(self)
                // Others queues await their turn
                objc_sync_enter(self)                   // Lock access until objc_sync_exit(self).
                self.variable += 1
                print("\(dispatchQueue), value: \(self.variable)")
                objc_sync_exit(self)                    // Unlock access
            }
            
            notifyWhenDone {
                objc_sync_enter(self)                   // Lock access until objc_sync_exit(self)
                print("variable = \(self.variable)")
                objc_sync_exit(self)                    // Unlock access
            }
        }
    }
    
    // Helpers
    
    extension Atomic {
    
        fileprivate func notifyWhenDone(closure: @escaping ()->()) {
            dispatchGroup.notify(queue: .global(qos: .utility)) {
                closure()
                print("All work done")
            }
        }
        
        fileprivate func runInSeveralQueues(closure: @escaping (DispatchQueue)->()) {
            
            async(dispatch: .main, closure: closure)
            async(dispatch: .global(qos: .userInitiated), closure: closure)
            async(dispatch: .global(qos: .utility), closure: closure)
            async(dispatch: .global(qos: .default), closure: closure)
            async(dispatch: .global(qos: .userInteractive), closure: closure)
        }
        
        private func async(dispatch: DispatchQueue, closure: @escaping (DispatchQueue)->()) {
            
            for _ in 0 ..< 100 {
                dispatchGroup.enter()
                dispatch.async {
                    let usec = Int(arc4random()) % 100_000
                    usleep(useconds_t(usec))
                    closure(dispatch)
                    self.dispatchGroup.leave()
                }
            }
        }
    }
    

    用法

    Atomic().semaphoreSample()
    //Atomic().dispatchQueueSync()
    //Atomic().objcSync()
    

    结果

    【讨论】:

    • 在 github 上有一个示例项目会很好!
    • 您好!这是完整的样本。复制Atomic 类并使用Atomic().semaphoreSample() 运行它
    • 是的,我已经这样做了。认为将它作为一个更新为最新语法的项目会很好。使用 Swift,语法一直在变化。而你的答案是迄今为止最新的一个:)
    【解决方案5】:

    从 Swift 5.1 开始,您可以使用 property wrappers 为您的属性创建特定逻辑。这是原子包装器实现:

    @propertyWrapper
    struct atomic<T> {
        private var value: T
        private let lock = NSLock()
    
        init(wrappedValue value: T) {
            self.value = value
        }
    
        var wrappedValue: T {
          get { getValue() }
          set { setValue(newValue: newValue) }
        }
    
        func getValue() -> T {
            lock.lock()
            defer { lock.unlock() }
    
            return value
        }
    
        mutating func setValue(newValue: T) {
            lock.lock()
            defer { lock.unlock() }
    
            value = newValue
        }
    }
    

    使用方法:

    class Shared {
        @atomic var value: Int
    ...
    }
    

    【讨论】:

      【解决方案6】:

      这是我广泛使用的原子属性包装器。我把实际的锁定机制做成了一个协议,所以我可以体验不同的机制。我尝试了信号量DispatchQueuespthread_rwlock_t。选择 pthread_rwlock_t 是因为它的开销似乎最低,并且优先级反转的可能性较低。

      /// Defines a basic signature that all locks will conform to. Provides the basis for atomic access to stuff.
      protocol Lock {
          init()
          /// Lock a resource for writing. So only one thing can write, and nothing else can read or write.
          func writeLock()
          /// Lock a resource for reading. Other things can also lock for reading at the same time, but nothing else can write at that time.
          func readLock()
          /// Unlock a resource
          func unlock()
      }
      
      final class PThreadRWLock: Lock {
          private var rwLock = pthread_rwlock_t()
      
          init() {
              guard pthread_rwlock_init(&rwLock, nil) == 0 else {
                  preconditionFailure("Unable to initialize the lock")
              }
          }
      
          deinit {
              pthread_rwlock_destroy(&rwLock)
          }
      
          func writeLock() {
              pthread_rwlock_wrlock(&rwLock)
          }
      
          func readLock() {
              pthread_rwlock_rdlock(&rwLock)
          }
      
          func unlock() {
              pthread_rwlock_unlock(&rwLock)
          }
      }
      
      /// A property wrapper that ensures atomic access to a value. IE only one thing can write at a time.
      /// Multiple things can potentially read at the same time, just not during a write.
      /// By using `pthread` to do the locking, this safer then using a `DispatchQueue/barrier` as there isn't a chance
      /// of priority inversion.
      @propertyWrapper
      public final class Atomic<Value> {
      
          private var value: Value
          private let lock: Lock = PThreadRWLock()
      
          public init(wrappedValue value: Value) {
              self.value = value
          }
      
          public var wrappedValue: Value {
              get {
                  self.lock.readLock()
                  defer { self.lock.unlock() }
                  return self.value
              }
              set {
                  self.lock.writeLock()
                  self.value = newValue
                  self.lock.unlock()
              }
          }
      
          /// Provides a closure that will be called synchronously. This closure will be passed in the current value
          /// and it is free to modify it. Any modifications will be saved back to the original value.
          /// No other reads/writes will be allowed between when the closure is called and it returns.
          public func mutate(_ closure: (inout Value) -> Void) {
              self.lock.writeLock()
              closure(&value)
              self.lock.unlock()
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2023-01-26
        • 1970-01-01
        • 2012-04-23
        • 2011-01-18
        • 2011-02-22
        • 2020-02-03
        • 2020-03-09
        相关资源
        最近更新 更多