HCN DEV

한국어로 보기

atomic/non atomic

atomic/non atomic feature image

The concept of atomic is familiar to those who have experience with Objective-C but may be unfamiliar to those who have only used Swift. Let’s examine the definition of atomic:

  • atomic - Not interrupted

an operation appears to occur at a single instant between its invocation and its response

Being atomic in programming means making changes to data appear as if they happened all at once. Changing the value of data always takes some time. However, atomic data gives the perception to multiple data consumers (processes, threads, etc.) that the time it takes to change the data’s value is zero. How is this possible? In Objective-C, when you set atomic data, it locks it to make it atomic.

if (!atomic) {
    oldValue = *slot;
    *slot = newValue;
} else {
    spin_lock_t *slotlock = &PropertyLocks[GOODHASH(slot)];
    _spin_lock(slotlock);
    oldValue = *slot;
    *slot = newValue;
    _spin_unlock(slotlock);
}

Locking a thread means that everything other than the data change operation stops when changing the data (more precisely, when a SpinLock is used, the thread enters a loop and busy waits until all operations in the critical section are completed). So, in other words, it makes it seem like the data changed instantly without any time-consuming operations because nothing else happened except for updating the data. In this way, atomic data guarantees that in a multi-threaded environment, data can only be accessed in the situation before and after the change. That is, during the data change, access to that data is not possible (because it’s locked).

atomic and Objective-C

In Objective-C, properties are declared as atomic by default. However, atomic properties, as seen earlier, slow down property access because they lock the atomic data during access. Therefore, for many Objective-C properties that should only be updated on the main thread (such as View Properties), it’s advisable to set the nonatomic annotation.

@interface Asset : NSObject
// Use 'nonatomic' for the 'name' property.
@property (nonatomic, strong) NSString *name;
@end

atomic and Swift

On the other hand, Swift is not a language designed with thread-safety in mind, so all properties in Swift are non atomic by default. Additionally, you cannot specify the atomic option separately in Swift. Therefore, to make Swift properties support atomic, you need to implement it using GCD (Grand Central Dispatch). You can find more details in the following article. Here, we’ll provide a simple example based on the article.

class AtomicValue<T> {
    let queue = DispatchQueue(label: "queue")

    private(set) var storedValue: T

    init(_ storedValue: T) {
        self.storedValue = storedValue
    }

    var value: T {
        get {
            return queue.sync {
                self.storedValue
            }
        }
        set { // Reading and writing access is atomic by itself,
              // but access is possible by other threads during data change (between read and write), so it's not perfectly atomic.
            queue.sync {
                self.storedValue = newValue
            }
        }
    }

    // The correct way
    func mutate(_ transform: (inout T) -> ()) {
        queue.sync {
            transform(&self.storedValue)
        }
    }
}

let atomicInComplete = AtomicValue<Int>(0)
let atomicComplete = AtomicValue<Int>(0)
DispatchQueue.concurrentPerform(iterations: 100) { (idx) in

    atomicInComplete.value += idx

    atomicComplete.mutate { $0 += idx }
}

print(atomicInComplete.storedValue) // Result: Varies each time it's run
print(atomicComplete.storedValue) // Result: 4950

References