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

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

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

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

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

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

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

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

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

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

( Примечание: как правильно заметил Эрик OSAtomicIncrement32 , OSAtomicIncrement32 и друзья устарели от macOS 10.12 / iOS 10.10. Xcode 8 предлагает вместо этого использовать функции из . Однако это кажется трудным, сравните 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 для нескольких streamов, вызывающих проблемы (поскольку массивы не являются streamобезопасными) – как мне обойти это? или GCD со статическими функциями структуры для более сложных примеров. Этот stream Какие преимущества у 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 } } 

Здесь доступна последняя версия этого classа.

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

Результаты

введите описание изображения здесь

Я улучшил ответ от @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") } } } 
  • Как продемонстрировать условия гонки вокруг ценностей, которые не опубликованы должным образом?
  • Atomic UPDATE .. SELECT в Postgres
  • Пункты памяти и стиль кодирования по Java VM
  • Как мне вызвать некоторый метод блокировки с тайм-аутом в Java?
  • Зачем использовать ReentrantLock, если вы можете использовать синхронизированный (это)?
  • Безопасно ли читать указатель на функцию одновременно без блокировки?
  • RxJava вместо AsyncTask?
  • Как асинхронно вызывать метод в Java
  • Инструкции SSE: какие процессоры могут выполнять атомные операции памяти 16B?
  • Кажется, что сервлет обрабатывает несколько одновременных запросов браузера синхронно
  • Неправильная публикация ссылки на объект Java
  • Давайте будем гением компьютера.