Подтвердить что ты не робот

Как я могу атомизировать приращение переменной в Swift?

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

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

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

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

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

Ответ 1

Из API-интерфейсы низкого уровня Concurrency:

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

Эти функции работают с целыми числами фиксированного размера, вы можете выбрать 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 для синхронизации. Из Dispatch Queues в "Руководстве по программированию Concurrency":

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

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

// 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? также очень интересно.

Ответ 2

В этом случае очереди являются излишним. Вы можете использовать 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
    }
}

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

Ответ 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
  }
}

Ответ 4

подробности

  • Xcode 10.1 (10B61)
  • Swift 4

Решение

import Foundation

class Atomic<T> {

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

    init (value: T) { _value = value }

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

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

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

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

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

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

let atomicValue = Atomic(value: 0)

// Single actions with value

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

// Multi-operations 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()
            }
        }
    }
}

Результаты

enter image description here

Ответ 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") }
    }

}