How do I atomically increment a variable in Swift?
Solution 1
From Low-Level Concurrency APIs:
There’s a long list of OSAtomicIncrement and OSAtomicDecrement functions that allow you to increment and decrement an integer value in an atomic way – thread safe without having to take a lock (or use queues). These can be useful if you need to increment global counters from multiple threads for statistics. If all you do is increment a global counter, the barrier-free OSAtomicIncrement versions are fine, and when there’s no contention, they’re cheap to call.
These functions work with fixed-size integers, you can choose the 32-bit or 64-bit variant depending on your needs:
class Counter {
private (set) var value : Int32 = 0
func increment () {
OSAtomicIncrement32(&value)
}
}
(Note: As Erik Aigner correctly noticed, OSAtomicIncrement32
and
friends are deprecated as of macOS 10.12/iOS 10.10. Xcode 8 suggests to use functions from <stdatomic.h>
instead. However that seems to be difficult,
compare Swift 3: atomic_compare_exchange_strong and https://openradar.appspot.com/27161329.
Therefore the following GCD-based approach seems to be the best
solution now.)
Alternatively, one can use a GCD queue for synchronization. From Dispatch Queues in the "Concurrency Programming Guide":
... With dispatch queues, you could add both tasks to a serial dispatch queue to ensure that only one task modified the resource at any given time. This type of queue-based synchronization is more efficient than locks because locks always require an expensive kernel trap in both the contested and uncontested cases, whereas a dispatch queue works primarily in your application’s process space and only calls down to the kernel when absolutely necessary.
In your case that would be
// Swift 2:
class Counter {
private var queue = dispatch_queue_create("your.queue.identifier", DISPATCH_QUEUE_SERIAL)
private (set) var value: Int = 0
func increment() {
dispatch_sync(queue) {
value += 1
}
}
}
// Swift 3:
class Counter {
private var queue = DispatchQueue(label: "your.queue.identifier")
private (set) var value: Int = 0
func increment() {
queue.sync {
value += 1
}
}
}
See Adding items to Swift array across multiple threads causing issues (because arrays aren't thread safe) - how do I get around that? or GCD with static functions of a struct for more sophisticated examples. This thread What advantage(s) does dispatch_sync have over @synchronized? is also very interesting.
Solution 2
Queues are an overkill in this case. You can use a DispatchSemaphore
introduced in Swift 3 for this purpose like so:
import Foundation
public class AtomicInteger {
private let lock = DispatchSemaphore(value: 1)
private var value = 0
// You need to lock on the value when reading it too since
// there are no volatile variables in Swift as of today.
public func get() -> Int {
lock.wait()
defer { lock.signal() }
return value
}
public func set(_ newValue: Int) {
lock.wait()
defer { lock.signal() }
value = newValue
}
public func incrementAndGet() -> Int {
lock.wait()
defer { lock.signal() }
value += 1
return value
}
}
The latest version of the class is available over here.
Solution 3
I know this question is already a little bit older, but I just recently stumbled upon the same problem. After researching a little and reading posts like http://www.cocoawithlove.com/blog/2016/06/02/threads-and-mutexes.html I came up with this solution for an atomic counter. Maybe it will also help others.
import Foundation
class AtomicCounter {
private var mutex = pthread_mutex_t()
private var counter: UInt = 0
init() {
pthread_mutex_init(&mutex, nil)
}
deinit {
pthread_mutex_destroy(&mutex)
}
func incrementAndGet() -> UInt {
pthread_mutex_lock(&mutex)
defer {
pthread_mutex_unlock(&mutex)
}
counter += 1
return counter
}
}
Solution 4
Details
- Xcode 10.1 (10B61)
- Swift 4.2
Solution
import Foundation
struct AtomicInteger<Type>: BinaryInteger where Type: BinaryInteger {
typealias Magnitude = Type.Magnitude
typealias IntegerLiteralType = Type.IntegerLiteralType
typealias Words = Type.Words
fileprivate var value: Type
private var semaphore = DispatchSemaphore(value: 1)
fileprivate func _wait() { semaphore.wait() }
fileprivate func _signal() { semaphore.signal() }
init() { value = Type() }
init(integerLiteral value: AtomicInteger.IntegerLiteralType) {
self.value = Type(integerLiteral: value)
}
init<T>(_ source: T) where T : BinaryInteger {
value = Type(source)
}
init(_ source: Int) {
value = Type(source)
}
init<T>(clamping source: T) where T : BinaryInteger {
value = Type(clamping: source)
}
init?<T>(exactly source: T) where T : BinaryInteger {
guard let value = Type(exactly: source) else { return nil }
self.value = value
}
init<T>(truncatingIfNeeded source: T) where T : BinaryInteger {
value = Type(truncatingIfNeeded: source)
}
init?<T>(exactly source: T) where T : BinaryFloatingPoint {
guard let value = Type(exactly: source) else { return nil }
self.value = value
}
init<T>(_ source: T) where T : BinaryFloatingPoint {
value = Type(source)
}
}
// Instance Properties
extension AtomicInteger {
var words: Type.Words {
_wait(); defer { _signal() }
return value.words
}
var bitWidth: Int {
_wait(); defer { _signal() }
return value.bitWidth
}
var trailingZeroBitCount: Int {
_wait(); defer { _signal() }
return value.trailingZeroBitCount
}
var magnitude: Type.Magnitude {
_wait(); defer { _signal() }
return value.magnitude
}
}
// Type Properties
extension AtomicInteger {
static var isSigned: Bool { return Type.isSigned }
}
// Instance Methods
extension AtomicInteger {
func quotientAndRemainder(dividingBy rhs: AtomicInteger<Type>) -> (quotient: AtomicInteger<Type>, remainder: AtomicInteger<Type>) {
_wait(); defer { _signal() }
rhs._wait(); defer { rhs._signal() }
let result = value.quotientAndRemainder(dividingBy: rhs.value)
return (AtomicInteger(result.quotient), AtomicInteger(result.remainder))
}
func signum() -> AtomicInteger<Type> {
_wait(); defer { _signal() }
return AtomicInteger(value.signum())
}
}
extension AtomicInteger {
fileprivate static func atomicAction<Result, Other>(lhs: AtomicInteger<Type>,
rhs: Other, closure: (Type, Type) -> (Result)) -> Result where Other : BinaryInteger {
lhs._wait(); defer { lhs._signal() }
var rhsValue = Type(rhs)
if let rhs = rhs as? AtomicInteger {
rhs._wait(); defer { rhs._signal() }
rhsValue = rhs.value
}
let result = closure(lhs.value, rhsValue)
return result
}
fileprivate static func atomicActionAndResultSaving<Other>(lhs: inout AtomicInteger<Type>,
rhs: Other, closure: (Type, Type) -> (Type)) where Other : BinaryInteger {
lhs._wait(); defer { lhs._signal() }
var rhsValue = Type(rhs)
if let rhs = rhs as? AtomicInteger {
rhs._wait(); defer { rhs._signal() }
rhsValue = rhs.value
}
let result = closure(lhs.value, rhsValue)
lhs.value = result
}
}
// Math Operator Functions
extension AtomicInteger {
static func != <Other>(lhs: AtomicInteger, rhs: Other) -> Bool where Other : BinaryInteger {
return atomicAction(lhs: lhs, rhs: rhs) { $0 != $1 }
}
static func != (lhs: AtomicInteger, rhs: AtomicInteger) -> Bool {
return atomicAction(lhs: lhs, rhs: rhs) { $0 != $1 }
}
static func % (lhs: AtomicInteger, rhs: AtomicInteger) -> AtomicInteger {
let value = atomicAction(lhs: lhs, rhs: rhs) { $0 % $1 }
return self.init(value)
}
static func %= (lhs: inout AtomicInteger, rhs: AtomicInteger) {
atomicActionAndResultSaving(lhs: &lhs, rhs: rhs) { $0 % $1 }
}
static func & (lhs: AtomicInteger, rhs: AtomicInteger) -> AtomicInteger {
let value = atomicAction(lhs: lhs, rhs: rhs) { $0 & $1 }
return self.init(value)
}
static func &= (lhs: inout AtomicInteger, rhs: AtomicInteger) {
atomicActionAndResultSaving(lhs: &lhs, rhs: rhs) { $0 & $1 }
}
static func * (lhs: AtomicInteger, rhs: AtomicInteger) -> AtomicInteger {
let value = atomicAction(lhs: lhs, rhs: rhs) { $0 * $1 }
return self.init(value)
}
static func *= (lhs: inout AtomicInteger, rhs: AtomicInteger) {
atomicActionAndResultSaving(lhs: &lhs, rhs: rhs) { $0 * $1 }
}
static func + (lhs: AtomicInteger, rhs: AtomicInteger) -> AtomicInteger {
let value = atomicAction(lhs: lhs, rhs: rhs) { $0 + $1 }
return self.init(value)
}
static func += (lhs: inout AtomicInteger, rhs: AtomicInteger) {
atomicActionAndResultSaving(lhs: &lhs, rhs: rhs) { $0 + $1 }
}
static func - (lhs: AtomicInteger, rhs: AtomicInteger) -> AtomicInteger {
let value = atomicAction(lhs: lhs, rhs: rhs) { $0 - $1 }
return self.init(value)
}
static func -= (lhs: inout AtomicInteger, rhs: AtomicInteger) {
atomicActionAndResultSaving(lhs: &lhs, rhs: rhs) { $0 - $1 }
}
static func / (lhs: AtomicInteger, rhs: AtomicInteger) -> AtomicInteger {
let value = atomicAction(lhs: lhs, rhs: rhs) { $0 / $1 }
return self.init(value)
}
static func /= (lhs: inout AtomicInteger, rhs: AtomicInteger) {
atomicActionAndResultSaving(lhs: &lhs, rhs: rhs) { $0 / $1 }
}
}
// Shifting Operator Functions
extension AtomicInteger {
static func << <RHS>(lhs: AtomicInteger<Type>, rhs: RHS) -> AtomicInteger where RHS : BinaryInteger {
let value = atomicAction(lhs: lhs, rhs: rhs) { $0 << $1 }
return self.init(value)
}
static func <<= <RHS>(lhs: inout AtomicInteger, rhs: RHS) where RHS : BinaryInteger {
atomicActionAndResultSaving(lhs: &lhs, rhs: rhs) { $0 << $1 }
}
static func >> <RHS>(lhs: AtomicInteger, rhs: RHS) -> AtomicInteger where RHS : BinaryInteger {
let value = atomicAction(lhs: lhs, rhs: rhs) { $0 >> $1 }
return self.init(value)
}
static func >>= <RHS>(lhs: inout AtomicInteger, rhs: RHS) where RHS : BinaryInteger {
atomicActionAndResultSaving(lhs: &lhs, rhs: rhs) { $0 >> $1 }
}
}
// Comparing Operator Functions
extension AtomicInteger {
static func < <Other>(lhs: AtomicInteger<Type>, rhs: Other) -> Bool where Other : BinaryInteger {
return atomicAction(lhs: lhs, rhs: rhs) { $0 < $1 }
}
static func <= (lhs: AtomicInteger, rhs: AtomicInteger) -> Bool {
return atomicAction(lhs: lhs, rhs: rhs) { $0 <= $1 }
}
static func == <Other>(lhs: AtomicInteger, rhs: Other) -> Bool where Other : BinaryInteger {
return atomicAction(lhs: lhs, rhs: rhs) { $0 == $1 }
}
static func > <Other>(lhs: AtomicInteger, rhs: Other) -> Bool where Other : BinaryInteger {
return atomicAction(lhs: lhs, rhs: rhs) { $0 > $1 }
}
static func > (lhs: AtomicInteger, rhs: AtomicInteger) -> Bool {
return atomicAction(lhs: lhs, rhs: rhs) { $0 > $1 }
}
static func >= (lhs: AtomicInteger, rhs: AtomicInteger) -> Bool {
return atomicAction(lhs: lhs, rhs: rhs) { $0 >= $1 }
}
static func >= <Other>(lhs: AtomicInteger, rhs: Other) -> Bool where Other : BinaryInteger {
return atomicAction(lhs: lhs, rhs: rhs) { $0 >= $1 }
}
}
// Binary Math Operator Functions
extension AtomicInteger {
static func ^ (lhs: AtomicInteger, rhs: AtomicInteger) -> AtomicInteger {
let value = atomicAction(lhs: lhs, rhs: rhs) { $0 ^ $1 }
return self.init(value)
}
static func ^= (lhs: inout AtomicInteger, rhs: AtomicInteger) {
atomicActionAndResultSaving(lhs: &lhs, rhs: rhs) { $0 ^ $1 }
}
static func | (lhs: AtomicInteger, rhs: AtomicInteger) -> AtomicInteger {
let value = atomicAction(lhs: lhs, rhs: rhs) { $0 | $1 }
return self.init(value)
}
static func |= (lhs: inout AtomicInteger, rhs: AtomicInteger) {
atomicActionAndResultSaving(lhs: &lhs, rhs: rhs) { $0 | $1 }
}
static prefix func ~ (x: AtomicInteger) -> AtomicInteger {
x._wait(); defer { x._signal() }
return self.init(x.value)
}
}
// Hashable
extension AtomicInteger {
var hashValue: Int {
_wait(); defer { _signal() }
return value.hashValue
}
func hash(into hasher: inout Hasher) {
_wait(); defer { _signal() }
value.hash(into: &hasher)
}
}
// Get/Set
extension AtomicInteger {
// Single actions
func get() -> Type {
_wait(); defer { _signal() }
return value
}
mutating func set(value: Type) {
_wait(); defer { _signal() }
self.value = value
}
// Multi-actions
func get(closure: (Type)->()) {
_wait(); defer { _signal() }
closure(value)
}
mutating func set(closure: (Type)->(Type)) {
_wait(); defer { _signal() }
self.value = closure(value)
}
}
Usage
// Usage Samples
let numA = AtomicInteger<Int8>(0)
let numB = AtomicInteger<Int16>(0)
let numC = AtomicInteger<Int32>(0)
let numD = AtomicInteger<Int64>(0)
var num1 = AtomicInteger<Int>(0)
num1 += 1
num1 -= 1
num1 = 10
num1 = num1/2
var num2 = 0
num2 = num1.get()
num1.set(value: num2*5)
// lock num1 to do several actions
num1.get { value in
//...
}
num1.set { value in
//...
return value
}
Full Sample
import Foundation
var x = AtomicInteger<Int>(0)
let dispatchGroup = DispatchGroup()
private func async(dispatch: DispatchQueue, closure: @escaping (DispatchQueue)->()) {
for _ in 0 ..< 100 {
dispatchGroup.enter()
dispatch.async {
print("Queue: \(dispatch.qos.qosClass)")
closure(dispatch)
dispatchGroup.leave()
}
}
}
func sample() {
let closure1: (DispatchQueue)->() = { _ in x += 1 }
let closure2: (DispatchQueue)->() = { _ in x -= 1 }
async(dispatch: .global(qos: .userInitiated), closure: closure1) // result: x += 100
async(dispatch: .global(qos: .utility), closure: closure1) // result: x += 100
async(dispatch: .global(qos: .background), closure: closure2) // result: x -= 100
async(dispatch: .global(qos: .default), closure: closure2) // result: x -= 100
}
sample()
dispatchGroup.wait()
print(x) // expected result x = 0
Solution 5
I improved on the answer from @florian, by using some overloaded operators :
import Foundation
class AtomicInt {
private var mutex = pthread_mutex_t()
private var integer: Int = 0
var value : Int {
return integer
}
//MARK: - lifecycle
init(_ value: Int = 0) {
pthread_mutex_init(&mutex, nil)
integer = value
}
deinit {
pthread_mutex_destroy(&mutex)
}
//MARK: - Public API
func increment() {
pthread_mutex_lock(&mutex)
defer {
pthread_mutex_unlock(&mutex)
}
integer += 1
}
func incrementAndGet() -> Int {
pthread_mutex_lock(&mutex)
defer {
pthread_mutex_unlock(&mutex)
}
integer += 1
return integer
}
func decrement() {
pthread_mutex_lock(&mutex)
defer {
pthread_mutex_unlock(&mutex)
}
integer -= 1
}
func decrementAndGet() -> Int {
pthread_mutex_lock(&mutex)
defer {
pthread_mutex_unlock(&mutex)
}
integer -= 1
return integer
}
//MARK: - overloaded operators
static func > (lhs: AtomicInt, rhs: Int) -> Bool {
return lhs.integer > rhs
}
static func < (lhs: AtomicInt, rhs: Int) -> Bool {
return lhs.integer < rhs
}
static func == (lhs: AtomicInt, rhs: Int) -> Bool {
return lhs.integer == rhs
}
static func > (lhs: Int, rhs: AtomicInt) -> Bool {
return lhs > rhs.integer
}
static func < (lhs: Int, rhs: AtomicInt) -> Bool {
return lhs < rhs.integer
}
static func == (lhs: Int, rhs: AtomicInt) -> Bool {
return lhs == rhs.integer
}
func test() {
let atomicInt = AtomicInt(0)
atomicInt.increment()
atomicInt.decrement()
if atomicInt > 10 { print("bigger than 10") }
if atomicInt < 10 { print("smaller than 10") }
if atomicInt == 10 { print("its 10") }
if 10 > atomicInt { print("10 is bigger") }
if 10 < atomicInt { print("10 is smaller") }
if 10 == atomicInt { print("its 10") }
}
}
Comments
-
fabrizioM almost 2 years
I want to be able to increment a counter atomically and I can't find any reference on how to do it.
Adding more information based on comments:
- Are you using GCD? No. I am not using GCD. Having to use a queue system to increment a number seems overkill.
- Do You understand basic thread safety? Yes I do otherwise I would not be asking about atomic increments.
- Is this variable local? No.
- Is it instance level? Yes it should be part of a single instance.
I want to do something like this:
class Counter { private var mux Mutex private (set) value Int func increment (){ mux.lock() value += 1 mux.unlock() } }
-
zneak almost 8 yearsUsing
OSAtomicIncrement32
with variables that aren't dumb fields (with get/set accessors or willSet/didSet observers) still compiles, but in case it wasn't obvious already, the compiler will insert additional instructions that make the operation as a whole dubiously atomic. Just a thing to keep an eye on. -
Joe Daniels over 7 yearsEven the
OSAtomic...
functions are deprecated, it seems an incredibly expensive solution to queue a block for execution just to do such a simple task... -
Patrick Goley over 7 yearsIs this truly threadsafe even though the getter for
value
is not also synchronized using the serial queue? It seems like a read fromvalue
could happen from any thread and get interleaved with the write tovalue
in the serial queue. -
Martin R over 7 years@PatrickGoley: You are completely right, the answer applies only to the "How do I atomically increment a variable?" question. If the variable is also read from different threads then the read should be synchronized with the same queue.
-
Dannie P over 6 yearsround of applause for the destroy in deinit
-
Era about 5 yearsQueue is always a terrible solution for synchronization. Use
DispatchSemaphore
instead. -
Florian Bauer over 4 yearsI am not sure. The benchmark you linked uses Objektiv-C, but this discussion is about Swift. The article that I linked says that the problem is that Swift needs to capture the closure when using dispatch_sync. And that it is this heap allocation, which makes it very slow.
-
mojuba over 4 yearsAre you sure a lock is needed when reading the value, i.e. in
get()
? -
Aleks N. over 4 years@mojuba Please read the comment in the code example to understand why lock on read is required.
-
hnh about 3 yearsNote that this is not proper in Swift:
var mutex = pthread_mutex_t()
. You need to allocate the memory for thepthread_mutex_t
yourself, e.g.let mutex = UnsafeMutablePointer<pthread_mutex_t>.allocate(capacity: 1)
. -
Palle about 2 yearsThis does not work: When mutating
bar
, Swift will first access the getter, which will acquire the lock, return the value and subsequently release the lock again. Then, the value is mutated, the lock is acquired again, the mutated value is written and the lock is released again. This can lead to the scenario, where two threads both first read the value (sequentially because of the lock), then both perform their edits and sequentially write back their results, such that the thread that happens to acquire the lock last determines the value, overwriting other concurrent writes. -
Era about 2 yearsYes that's true, but that is another problem. Mutating a value is always different. In that case you can add a custom method to your property wrapper that performs the mutation and access it via its projected value.
-
Palle about 2 yearsNo, that is not another problem. That is the problem asked about in the question.