atomic/non atomic
01 Mar 2019The 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).
- For a more detailed explanation of locks, you can refer to Atomic Properties in Swift.
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