Swift Atomics aims to allow system programmers to write synchronization constructs directly in Swift.
Swift Atomics provides developers aiming to build sophisticated, high-level, multi-threaded constructs, such as lock-free data structure, the opportunity to do so directly in Swift. Previously, to use atomic operations in Swift, developers had to usually resort to external libraries implemented in a different language.
While striving to make atomic operations easier to use for Swift developers, Swift does not remove the inherent risk with any program using low-level synchronization primitives and is not meant to be a library for every Swift developer out there.
The Atomics package provides carefully considered APIs for atomic operations that follows established design principles for Swift APIs. However, the underlying operations work on a very low level of abstraction. Atomics – even more than other low-level concurrency constructs – are notoriously difficult to use correctly.
So, most Swift developers will continue to use Grand Central Dispatch and queues for their asynchronous code.
The following is an example of how you can use Swift Atomics to write a program that increments a counter shared by multiple threads:
import Atomics
import Dispatch
let counter = ManagedAtomic<Int>(0)
DispatchQueue.concurrentPerform(iterations: 10) { _ in
for _ in 0 ..< 1_000_000 {
counter.wrappingIncrement(by: 1, ordering: .relaxed)
}
}
counter.load(ordering: .relaxed) // ⟹ 10_000_000
ManagedAtomic
is the easiest way to use atomic values, since it supports the usual reference counted behaviour. For cases where this is excessive overhead, UnsafeAtomic
is available, which can be used with any piece of memory for which you can get a pointer. Both ManagedAtomic
and UnsafeAtomic
provide the same set of operations, including load
, store
, exchange
, compareExchange
, and weakCompareExchange
.
Swift Atomics does not only support Int
, as shown in the example above, but a plethora of other types including the whole integers and unsigned integer family, boolean, pointers, references, enums, etc.
Code using atomic operations is not bound to Swift exclusivity memory access, which prevents variables from begin accessed under a different name while they are being modified in a different portion of the program. When using Swift 5, a violation of the exclusive access rule will usually cause a crash, but atomic operations are handled differently. This is made possible by a relaxation of the Swift memory model meant to make it possible to interoperate with low-level primitives imported from C/C++. So, the new rule that governs Swift Exclusivity Access states that:
Two accesses to the same variable aren't allowed to overlap unless both accesses are reads or both accesses are atomic.
In the above sentence, atomic access is defined as a call to a number of functions belonging to the C atomic operation library.
Swift Atomics extends Swift suitability for concurrent programming, although work on a real concurrency model, which will introduce some form of support for async/await and actors, is still in progress for Swift 6. In the next months, the Swift team will work to introduce tagged atomics and support for atomic floating point operations.