How do I atomically increment a variable in Swift?

后端 未结 7 747
别那么骄傲
别那么骄傲 2020-11-29 18:59

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 yo
相关标签:
7条回答
  • 2020-11-29 19:30

    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
      }
    }
    
    0 讨论(0)
  • 2020-11-29 19:30

    You can use @propertyWrappers for that

    @propertyWrapper
    struct AtomicValue<Value> {
    
        private let lock: NSLock
        private var value: Value
    
        init(default: Value) {
            self.lock = NSLock()
            self.value = `default`
        }
    
        var wrappedValue: Value {
            get {
                lock.lock()
                defer { lock.unlock() }
                return value
            }
            set {
                lock.lock()
                value = newValue
                lock.unlock()
            }
        }
    }
    

    Use it like so:

    class Foo {
    
        @AtomicValue(default: true)
        var bar: Bool
    }
    
    0 讨论(0)
  • 2020-11-29 19:34

    There are various approaches we can use to have atomically increment a variable in swift and has been discussed here.

    Also there is a swift proposal SE-0283 to add atomic variables natively in swift.

    0 讨论(0)
  • 2020-11-29 19:38

    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.

    0 讨论(0)
  • 2020-11-29 19:40

    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.

    0 讨论(0)
  • 2020-11-29 19:41

    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
    
    0 讨论(0)
提交回复
热议问题