In today modern apps multithreading is a de-facto standard for achieving concurrency. Multithreading comes with its own set of complexities i.e data race, dead lock, live lock etc. Apple provides various technologies for achieving multithreading on its platforms(macOS, iOS, iPadOS, watchOS and tvOS). These technologies include classic pthread
, Thread
(NSThread
), NSOperationQueue
and DispatchQueue
.
One of the important aspect of multi threading is atomic types. Atomic types are language primitives that provide access to value with guarantee that no data race conditions arise.
Popular programming languages like C++ and Java have well established support for atomic types. C++ has std::atomic
as part of standard library and Java has Atomic Variables as part of java.util.concurrent.atomic
package.
Unfortunately as of writing there is no language support for atomic types in Swift. To demonstrate how an atomic type can be implemented in Swift we will start with a simple use case
// A protocol to be used in our atomic type
protocol Operatable {
func add(other: Self) -> Self
}
final class Atomic<T: Operatable> {
private var t: T
init(_ t: T) {
self.t = t
}
func incrementAndGet(by other: T) -> T {
// Critical section starts
// The change to t should be atomic here
let result = t.add(other: other)
t = result
// Critical section ends
return result
}
}
In the above code snippet the implementation of incrementAndGet
method is not thread safe. Below is our test code
// We will only use Int for our demo but it can be made to work with any other type
extension Int: Operatable {
func add(other: Int) -> Int {
return self + other
}
}
func test() {
let atomicInt = Atomic<Int>(0)
for i in 1...5 {
let thread = Thread {
print("\(Thread.current.name!):Value:\(atomicInt.incrementAndGet(by: 1))")
}
thread.name = "Thread: \(i)"
thread.start()
}
Thread.sleep(forTimeInterval: 2)
}
Below is one of the possible output of above test code
Thread: 4: critical section started
Thread: 5: critical section started
Thread: 2: critical section started
Thread: 3: critical section started
Thread: 4: critical section ended
Thread: 5: critical section ended
Thread: 2: critical section ended
Thread: 1: critical section started
Thread: 3: critical section ended
Thread: 1: critical section ended
The is exactly the behaviour we do not want. What we want is at any given time there will at most one thread in critical section, thus it will make our type atomic. In below sections we will discuss various approaches to make the Atomic
class truly atomic.
Using NSLock
We can achieve atomicity using NSLock
from Foundation
framework. Below is a simple implementation of Atomic
class using NSLock
.
final class NSAtomic<T: Operatable> {
private var t: T
private let lock = NSLock()
init(_ t: T) {
self.t = t
}
func incrementAndGet(by other: T) -> T {
// Critical section starts
// The change to t should be atomic here
lock.lock()
print("\(Thread.current.name!): critical section started")
let result = t.add(other: other)
t = result
print("\(Thread.current.name!): critical section ended")
lock.unlock()
// Critical section ends
return result
}
}
func testNSAtomic() {
print("test with NSLock")
let atomicInt = NSAtomic<Int>(0)
for i in 1...5 {
let thread = Thread {
_ = atomicInt.incrementAndGet(by: 1)
}
thread.name = "Thread: \(i)"
thread.start()
}
Thread.sleep(forTimeInterval: 2)
}
We have just added a NSLock
instance variable to our atomic type. Below is the output of using our NSAtomic
class.
Thread: 1: critical section started
Thread: 1: critical section ended
Thread: 2: critical section started
Thread: 2: critical section ended
Thread: 3: critical section started
Thread: 3: critical section ended
Thread: 4: critical section started
Thread: 4: critical section ended
Thread: 5: critical section started
Thread: 5: critical section ended
The is the correct behaviour we want. Each thread wait outside critical section if there is another thread currently inside critical section. We have ensured that our value t
is only changed by only one thread at any given time.
Using Grand Central Dispatch (GCD)
Using GCD is Apple recommended way of achieving concurrency. We will modify our Atomic class and implement it using a serial dispatch queue.
final class GCDAtomic<T: Operatable> {
private let initial: T
private var t: T
private let dispatchQueue = DispatchQueue(label: "tech.swiftx.dispatchQueue")
init(_ t: T) {
self.t = t
initial = t
}
func incrementAndGet(by other: T) -> T {
var result = initial
dispatchQueue.sync {
// Critical section starts
// The change to t should be atomic here
print("\(Thread.current.name!): critical section started")
result = t.add(other: other)
t = result
print("\(Thread.current.name!): critical section ended")
// Critical section ends
}
return result
}
}
Below is the output of using GCDAtomic class
Thread: 1: critical section started
Thread: 1: critical section ended
Thread: 5: critical section started
Thread: 5: critical section ended
Thread: 3: critical section started
Thread: 3: critical section ended
Thread: 4: critical section started
Thread: 4: critical section ended
Thread: 2: critical section started
Thread: 2: critical section ended
Please note that the order of thread execution is not relevant. The important factor is that at any give time only one of the thread is executing in critical section. This has also same behaviour as using NSLock.
All the above code snippets can be found here.
This a lot of boilerplate code. Luckily you dont have to implement atomic behaviour from scratch for your swift project. There is a popular open source library for working with atomics types in swift named swift-atomics.(I am not associated with swift-atomics library).
Towards Native Swift Atomics
As of writing there is a proposal SE-0282 in swift evolution that promises atomic types in swift as part of swift standard library. If the proposal is accepted we will see first class atomic types in swift in future swift versions.
Important Links
NSLock: https://developer.apple.com/documentation/foundation/nslock
GCD: https://developer.apple.com/documentation/DISPATCH
Swift-atomics: https://github.com/glessard/swift-atomics
SE-0283: https://github.com/apple/swift-evolution/blob/master/proposals/0282-atomics.md
Gist: https://bit.ly/3gD9QV6
std::atomic: https://en.cppreference.com/w/cpp/atomic/atomic
java.util.concurrent.atomic: https://docs.oracle.com/javase/tutorial/essential/concurrency/atomicvars.html
If you liked the article please consider sharing it with other people. Also please subscribe to my mailing list to get latest updates immediately.