Как атомарно увеличить переменную в Swift?

Я хочу иметь возможность увеличивать счетчик атомарно, и я не могу найти ссылку на то, как это сделать.

Добавление дополнительной информации на основе комментариев:

  • Вы используете GCD? Я не использую GDC. Необходимость использовать систему очередей для увеличения числа кажется излишним.
  • Вы понимаете основную безопасность потока? Да, я бы не стал спрашивать об атомных приращениях.
  • Эта переменная является локальной? Нет.
  • Это уровень экземпляра? Да, это должно быть частью одного экземпляра.

Я хочу сделать что-то вроде этого:

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

8 ответов

Решение

Из API-интерфейсов низкого уровня параллелизма:

Существует длинный список функций OSAtomicIncrement и OSAtomicDecrement, которые позволяют вам увеличивать и уменьшать целочисленные значения атомарным способом - поточно-безопасно, без необходимости блокировки (или использования очередей). Это может быть полезно, если вам нужно увеличить глобальные счетчики из нескольких потоков для статистики. Если все, что вы делаете, это увеличиваете глобальный счетчик, то безошибочные версии OSAtomicIncrement хороши, и, когда нет споров, их дешево вызывать.

Эти функции работают с целыми числами фиксированного размера, вы можете выбрать 32-битный или 64-битный вариант в зависимости от ваших потребностей:

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

(Примечание: как правильно заметил Эрик Эйгнер, OSAtomicIncrement32 и друзья устарели с MacOS 10.12/iOS 10.10. Xcode 8 предлагает использовать функции из <stdatomic.h> вместо. Однако это кажется трудным, сравните Swift 3: atomic_compare_exchange_strong и https://openradar.appspot.com/27161329. Поэтому следующий подход, основанный на GCD, кажется лучшим решением в настоящее время.)

В качестве альтернативы можно использовать очередь GCD для синхронизации. Из очередей отправки в "Руководстве по программированию параллелизма":

... С помощью очередей отправки можно добавить обе задачи в очередь последовательной отправки, чтобы гарантировать, что только одна задача изменила ресурс в любой момент времени. Этот тип синхронизации на основе очередей более эффективен, чем блокировки, потому что для блокировок всегда требуется дорогостоящая ловушка ядра как в оспариваемых, так и не оспариваемых случаях, тогда как очередь отправки работает в основном в пространстве процессов вашего приложения и вызывает ядро ​​только тогда, когда это абсолютно необходимо.

В вашем случае это было бы

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

См. Добавление элементов в массив Swift между несколькими потоками, вызывающими проблемы (поскольку массивы не являются потокобезопасными) - как мне обойти это? или GCD со статическими функциями структуры для более сложных примеров. Этот поток Какие преимущества имеет dispatch_sync перед @synchronized? тоже очень интересно.

Очереди являются избыточным в этом случае. Вы можете использовать DispatchSemaphore введен в Swift 3 для этой цели так:

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

Последняя версия класса доступна здесь.

Я знаю, что этот вопрос уже немного старше, но я недавно наткнулся на ту же проблему. После небольшого исследования и чтения постов, таких как 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
  }
}

подробности

xCode 9.1, Swift 4

Решение

import Foundation

class Atomic<T> {

    private let semaphore = DispatchSemaphore(value: 1)
    private var _value: T

    var value: T {
        get {
            wait()
            let result = _value
            defer {
                signal()
            }
            return result
        }

        set (value) {
            wait()
            _value = value
            defer {
                signal()
            }
        }
    }

    func set(closure: (_ currentValue: T)->(T)){
        wait()
        _value = closure(_value)
        signal()
    }

    func get(closure: (_ currentValue: T)->()){
        wait()
        closure(_value)
        signal()
    }

    private func wait() {
        semaphore.wait()
    }

    private func signal() {
        semaphore.signal()
    }

    init (value: T) {
        _value = value
    }
}

использование

let atomicValue = Atomic(value: 0)

// Single actions with value

atomicValue.value = 0
print("value = \(atomicValue.value)")

// Multioperations with value

atomicValue.set{ (current) -> (Int) in
    print("value = \(current)")
    return current + 1
}
atomicValue.get{ (current) in
    print("value = \(current)")
}

Полный образец

import UIKit

class ViewController: UIViewController {

    var atomicValue = Atomic(value: 0)

    let dispatchGroup = DispatchGroup()

    override func viewDidLoad() {
        super.viewDidLoad()

        sample()

        dispatchGroup.notify(queue: .main) {
            print(self.atomicValue.value)
        }
    }

    func sample() {

        let closure:(DispatchQueue)->() = { dispatch in
            self.atomicValue.set{ (current) -> (Int) in
                print("\(dispatch), value = \(current)")
                return current + 1
            }
//            self.atomicValue.get{ (current) in
//                print("\(dispatch), value = \(current)")
//            }
        }

        async(dispatch: .main, closure: closure)
        async(dispatch: .global(qos: .userInitiated), closure: closure)
        async(dispatch: .global(qos: .utility), closure: closure)
    }

    private func async(dispatch: DispatchQueue, closure: @escaping (DispatchQueue)->()) {

        for _ in 0..<10 {
            dispatchGroup.enter()
            dispatch.async {
                closure(dispatch)
                self.dispatchGroup.leave()
            }
        }
    }
}

Результаты

Это оказалось наиболее упрощенным решением на момент написания, пока мы не получим правильный атомный тип Swift. Вы можете использовать этот универсальный класс для установки атомарного value любого вида.

Злоупотребляя DispatchQueue для этого нет смысла. Во-первых, вы ограничиваетесь блочной синхронизацией, создаете потенциально ненужные замыкания / стековые фреймы, просто для использования внутренних очередей DispatchSemaphore вместо того, чтобы создавать это напрямую.

final class Atomic<T> {

    private let sema = DispatchSemaphore(value: 1)
    private var _value: T

    init (_ value: T) {
        _value = value
    }

    var value: T {
        get {
            sema.wait()
            defer {
                sema.signal()
            }
            return _value
        }
        set {
            sema.wait()
            _value = newValue
            sema.signal()
        }
    }

    func swap(_ value: T) -> T {
        sema.wait()
        let v = _value
        _value = value
        sema.signal()
        return v
    }
}

Использование:

let atomicBool = Atomic<Bool>(false)
atomicBool.value = true

let atomicInt = Atomic<Int>(0)
let oldInt = atomicInt.swap(1)

Если вы хотите увеличить Ints, вот продолжение для этого

extension Atomic where T == Int {

    func increment(n: Int) -> Int {
        sema.wait()
        let v = _value + n
        _value = v
        sema.signal()
        return v
    }
}

Вы можете взглянуть на <tcode id="286924"></tcode> библиотека, размещенная Apple, которая поддерживает общие типы

Я улучшил ответ от @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") }
    }

}

Существуют различные подходы, которые мы можем использовать для атомарного увеличения переменной в быстром режиме, и они обсуждались здесь.

Также есть быстрое предложение SE-0283 о добавлении атомарных переменных изначально в Swift.

Другие вопросы по тегам