106

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

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

From my understanding you can read and write properties defined as atomic from multiple threads safely, while writing and accessing nonatomic properties or ivars from multiple threads at the same time can result in undefined behavior, including bad access errors.

So if you have a variable like this in Swift:

var object: NSObject

Can I read and write to this variable in parallel safely? (Without considering the actual meaning of doing this).

lassej
  • 6,256
  • 6
  • 26
  • 34
  • I think in future, maybe we can use `@atomic` or `@nonatomic`. or just atomic by default. (Swift is so incomplete, we can't tell much now) – Bryan Chen Jun 11 '14 at 08:48
  • 1
    IMO, they will make everything non-atomic by default, and probably provide a special feature to make atomic stuff. – eonil Jun 27 '14 at 02:32
  • As an aside, `atomic` is generally not considered sufficient for thread-safe interaction with a property, except for simple data types. For objects, one generally synchronizes access across threads using locks (e.g., `NSLock` or `@synchronized`) or GCD queues (e.g., serial queue or concurrent queue with "reader-writer" pattern). – Rob Aug 04 '14 at 18:07
  • @Rob, true, although due to reference counting in Objective-C (and possibly in Swift) concurrent reading and writing to a variable without atomic access can result in memory corruption. If all variables had atomic access the worst thing that could happen would be a "logical" race condition, i.e. unexpected behavior. – lassej Aug 05 '14 at 20:52
  • Don't get me wrong: I hope Apple answers/resolves the atomic behavior question. It's just that (a) `atomic` doesn't ensure thread-safety for objects; and (b) if one properly uses one of the aforementioned synchronization techniques to ensure thread-safety (amongst other things, preventing simultaneous read/writes), the atomic issue is moot. But we still need/want it for simple data types, where `atomic` has real value. Good question! – Rob Aug 05 '14 at 21:46
  • @Rod from your comment, 1. `atomic` makes sense for primitives only. 2. `atomic` does not ensure thread-safety for Objects, e.g. `NSObject` and its descenders. Correct? – Yevhen Dubinin Mar 24 '16 at 08:09
  • @Rob You are incorrect, atomic **is** sufficient for thead-safe interaction with a **property**. It may just not be sufficient for thead-safe interaction with an entire object, as sometimes two or more properties must be changed atomcially to represent a consistent state; e.g. an item must be added to a data structure and a counter must be increased - either both happens or none happens but if only one of both happens, the object is in a "broken" state. But if no such dependency exists, an object with all atomic properties is totally thread-safe itself. – Mecki May 22 '18 at 10:48
  • You mention one problem (the synchronization of that property with other properties of the broader context which is generally critical issue). The second issue is if the property, itself, references an object, then making the reference atomic doesn't make the referenced property thread-safe. It only makes the manipulation of that pointer thread-safe (i.e. the pointer cannot be corrupted), but if the underlying object it references is not thread-safe, it's for naught. `atomic` has some limited utility for fundamental data types, but beyond that, it's rarely sufficient. – Rob May 22 '18 at 14:44

6 Answers6

54

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.

Sash Zats
  • 5,376
  • 2
  • 28
  • 42
11

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.

Good Doug
  • 2,072
  • 15
  • 12
7

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.

ColinE
  • 68,894
  • 15
  • 164
  • 232
  • re: lack of KVO, check out `willSet`, `didSet` – seem to be a first step on the way – Sash Zats Jun 11 '14 at 09:03
  • 1
    willSet, didSet is more for properties that always needed a custom setter because they had to do something. For example, a color property that needs to redraw a view when the property is changed to a different value; that is now done easier using didSet. – gnasher729 Jun 11 '14 at 09:22
  • yes, that's what I meant by "a first step":) I assumed it might be a sign of the feature being available but not fully implemented yet – Sash Zats Jun 11 '14 at 10:43
7

Details

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

Links

Implemented types

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

enter image description here

Vasily Bodnarchuk
  • 24,482
  • 9
  • 132
  • 127
  • A sample project on github would be nice! – Klaas Nov 28 '17 at 09:44
  • 1
    Hello! This is full sample. Copy the `Atomic` class and run it using `Atomic().semaphoreSample()` – Vasily Bodnarchuk Nov 28 '17 at 09:51
  • Yes, I already did. Thought it would be nice, to have it as a project that get's updated to the most current syntax. With Swift the syntax changes all the time. And your answer is by far the most recent one :) – Klaas Nov 28 '17 at 09:55
6

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
...
}
iUrii
  • 11,742
  • 1
  • 33
  • 48
3

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()
    }
}
jamone
  • 17,253
  • 17
  • 63
  • 98