【问题标题】:How do I atomically increment a variable in Swift?如何在 Swift 中原子地增加一个变量?
【发布时间】:2015-08-31 07:30:37
【问题描述】:

我希望能够以原子方式递增计数器,但我找不到任何有关如何执行此操作的参考。

基于cmets添加更多信息:

  • 您在使用 GCD 吗?不,我没有使用 GCD。不得不使用队列系统来增加一个数字似乎有点过分了。
  • 了解你了解基本的线程安全吗?是的,否则我不会询问原子增量。
  • 这个变量是本地的吗?没有。
  • 它是实例级别的吗? 是的,它应该是单个实例的一部分。

我想做这样的事情:

 class Counter {
      private var mux Mutex
      private (set) value Int
      func increment (){
          mux.lock()
          value += 1
          mux.unlock()
      }
 }

【问题讨论】:

标签: swift concurrency


【解决方案1】:

来自Low-Level Concurrency APIs

有很长的 OSAtomicIncrement 和 OSAtomicDecrement 列表 允许您增加和减少整数值的函数 以原子方式——线程安全,无需锁(或使用 队列)。如果您需要增加全局计数器,这些会很有用 从多个线程进行统计。如果你所做的只是增加一个 全局计数器,无障碍 OSAtomicIncrement 版本很好, 当没有争用时,它们的调用成本很低。

这些函数适用于固定大小的整数,您可以选择 32 位或 64 位版本取决于您的需要:

class Counter {
    private (set) var value : Int32 = 0
    func increment () {
        OSAtomicIncrement32(&value)
    }
}

(注意:正如 Erik Aigner 正确注意到的那样,OSAtomicIncrement32 和 从 macOS 10.12/iOS 10.10 起,friends 已被弃用。 Xcode 8 建议改用<stdatomic.h> 中的函数。然而这似乎很难, 比较Swift 3: atomic_compare_exchange_stronghttps://openradar.appspot.com/27161329。 因此,以下基于 GCD 的方法似乎是最好的 立即解决。)

或者,可以使用 GCD 队列进行同步。 来自《并发编程指南》中的Dispatch Queues

... 使用调度队列,您可以将两个任务添加到串行 调度队列以确保只有一个任务在 任何给定的时间。这种基于队列的同步比较多 比锁更高效,因为锁总是需要昂贵的内核 在有争议和无争议的案件中都陷入陷阱,而派遣 队列主要在您的应用程序的进程空间中工作,并且仅 在绝对必要时调用内核。

在你的情况下

// 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
        }
    }
}

有关更复杂的示例,请参阅 Adding items to Swift array across multiple threads causing issues (because arrays aren't thread safe) - how do I get around that?GCD with static functions of a struct。这个线程 What advantage(s) does dispatch_sync have over @synchronized?也很有意思。

【讨论】:

  • OSAtomicIncrement32 与非哑字段变量(使用 get/set 访问器或 willSet/didSet 观察器)一起使用仍然可以编译,但如果它还不明显,编译器将插入额外的使操作作为一个整体可疑的原子的指令。只是需要注意的事情。
  • 即使 OSAtomic... 函数已被弃用,为了执行如此简单的任务而将块排队等待执行似乎是一个非常昂贵的解决方案......
  • 即使value 的getter 也没有使用串行队列同步,这真的是线程安全的吗?似乎从任何线程读取value 都可能发生,并与串行队列中对value 的写入交错。
  • @PatrickGoley:您完全正确,答案仅适用于 “如何以原子方式递增变量?” 问题。如果变量也是从不同的线程读取的,那么读取应该与同一个队列同步。
  • 队列对于同步来说总是一个糟糕的解决方案。请改用DispatchSemaphore
【解决方案2】:

在这种情况下,队列是多余的。为此,您可以使用 Swift 3 中引入的DispatchSemaphore,如下所示:

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

最新版本的课程在over here可用。

【讨论】:

  • 在读取值时确定需要锁吗,即在get()中?
  • @mojuba 请阅读代码示例中的注释以了解为什么需要锁定读取。
【解决方案3】:

我知道这个问题已经有点老了,但我最近偶然发现了同样的问题。 在进行了一些研究并阅读了http://www.cocoawithlove.com/blog/2016/06/02/threads-and-mutexes.html 之类的帖子后,我想出了这个原子计数器的解决方案。也许它也会帮助其他人。

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

【讨论】:

  • 为 deinit 中的破坏鼓掌
  • 我不确定。您链接的基准测试使用 Objektiv-C,但此讨论是关于 Swift 的。我链接的文章说问题是Swift在使用dispatch_sync时需要捕获闭包。正是这种堆分配,使它变得非常慢。
  • 请注意,这在 Swift 中是不合适的:var mutex = pthread_mutex_t()。您需要自己为pthread_mutex_t 分配内存,例如let mutex = UnsafeMutablePointer<pthread_mutex_t>.allocate(capacity: 1).
【解决方案4】:

详情

  • Xcode 10.1 (10B61)
  • 斯威夫特 4.2

解决方案

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

完整样本

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

【讨论】:

    【解决方案5】:

    我通过使用一些重载运算符改进了@florian 的答案:

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

    【讨论】:

      【解决方案6】:

      您可以为此使用@propertyWrappers

      @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()
              }
          }
      }
      

      像这样使用它:

      class Foo {
      
          @AtomicValue(default: true)
          var bar: Bool
      }
      

      【讨论】:

        【解决方案7】:

        您可以查看 Apple 提供的支持通用类型的 Swift Atomics

        [Concurrency theory]

        【讨论】:

          【解决方案8】:

          我们可以使用多种方法在 swift 中自动增加变量,并已在here 中进行了讨论。

          还有一个 swift 提议 SE-0283 在 swift 中本地添加原子变量。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-12-17
            • 2012-03-19
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多