Are Swift variables atomic?

前端 未结 6 1168
隐瞒了意图╮
隐瞒了意图╮ 2020-11-28 04:19

In Objective-C you have a distinction between atomic and nonatomic properties:

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


        
相关标签:
6条回答
  • 2020-11-28 04:45

    It is probably to early to answer this question. Currently swift lacks access modifiers, so there is not obvious way to add code which manages concurrency around a properties getter / setter. Furthermore, the Swift Language doesn't seem to have any information about concurrency yet! (It also lacks KVO etc ...)

    I think the answer to this question will become clear in future releases.

    0 讨论(0)
  • Details

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

    Links

    • apple.developer.com Dispatch
    • Grand Central Dispatch (GCD) and Dispatch Queues in Swift 3
    • Creating Thread-Safe Arrays in Swift
    • Mutexes and closure capture in Swift

    Implemented types

    • QueueSafeValue - a universal wrapper that provides safe multi-threaded access to values
    • AtomicArray
    • AtomicInteger
    • AtomicValue

    Main Idea

    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
            }
        }
    }
    

    Sample of atomic 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()
                }
            }
        }
    }
    

    Usage

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

    Result

    0 讨论(0)
  • 2020-11-28 04:54

    From Swift 5.1 you can use property wrappers to make specific logic for your properties. This is atomic wrapper implementation:

    @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
        }
    }
    

    How to use:

    class Shared {
        @atomic var value: Int
    ...
    }
    
    0 讨论(0)
  • 2020-11-28 04:55

    Swift has no language constructs around thread safety. It is assumed that you will be using the provided libraries to do your own thread safety management. There are a large number of options you have in implementing thread safety including pthread mutexes, NSLock, and dispatch_sync as a mutex mechanism. See Mike Ash's recent post on the subject: https://mikeash.com/pyblog/friday-qa-2015-02-06-locks-thread-safety-and-swift.html So the direct answer to your question of "Can I read and write to this variable in parallel safely?" is No.

    0 讨论(0)
  • 2020-11-28 04:56

    It's very early to assume as no low-level documentation is available, but you can study from assembly. Hopper Disassembler is a great tool.

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

    Uses objc_storeStrong and objc_setProperty_atomic for nonatomic and atomic respectively, where

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

    uses swift_retain from libswift_stdlib_core and, apparently, does not have thread safety built in.

    We can speculate that additional keywords (similar to @lazy) might be introduced later on.

    Update 07/20/15: according to this blogpost on singletons swift environment can make certain cases thread safe for you, i.e.:

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

    Update 05/25/16: Keep an eye out for swift evolution proposal https://github.com/apple/swift-evolution/blob/master/proposals/0030-property-behavior-decls.md - it looks like it is going to be possible to have @atomic behavior implemented by yourself.

    0 讨论(0)
  • 2020-11-28 05:02

    Here is the atomic property wrapper that I use extensively. I made the actual locking mechanism a protocol, so I could experiement with different mechanisms. I tried semaphores, DispatchQueues, and the pthread_rwlock_t. The pthread_rwlock_t was chosen because it appears to have the lowest overhead, and a lower chance of a priority inversion.

    /// 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()
        }
    }
    
    0 讨论(0)
提交回复
热议问题