How do I atomically increment a variable in Swift?

30,484

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") }
    }

}
Share:
30,484
fabrizioM
Author by

fabrizioM

I am an artist

Updated on July 09, 2022

Comments

  • fabrizioM
    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
    zneak almost 8 years
    Using 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
    Joe Daniels over 7 years
    Even 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
    Patrick Goley over 7 years
    Is this truly threadsafe even though the getter for value is not also synchronized using the serial queue? It seems like a read from value could happen from any thread and get interleaved with the write to value in the serial queue.
  • Martin R
    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
    Dannie P over 6 years
    round of applause for the destroy in deinit
  • Era
    Era about 5 years
    Queue is always a terrible solution for synchronization. Use DispatchSemaphore instead.
  • Florian Bauer
    Florian Bauer over 4 years
    I 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
    mojuba over 4 years
    Are you sure a lock is needed when reading the value, i.e. in get()?
  • Aleks N.
    Aleks N. over 4 years
    @mojuba Please read the comment in the code example to understand why lock on read is required.
  • hnh
    hnh about 3 years
    Note that this is not proper in Swift: var mutex = pthread_mutex_t(). You need to allocate the memory for the pthread_mutex_t yourself, e.g. let mutex = UnsafeMutablePointer<pthread_mutex_t>.allocate(capacity: 1).
  • Palle
    Palle about 2 years
    This 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
    Era about 2 years
    Yes 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
    Palle about 2 years
    No, that is not another problem. That is the problem asked about in the question.