Come faccio a incrementare atomicamente una variabile in Swift?

Voglio essere in grado di incrementare un contatore atomicamente e non riesco a trovare alcun riferimento su come farlo.

Aggiunta di ulteriori informazioni in base ai commenti:

  • Stai usando GCD? No. Non sto usando GDC. Dovendo usare un sistema di coda per incrementare un numero sembra eccessivo.
  • Sei consapevole della sicurezza del thread di base? Sì, lo farei altrimenti non chiederei circa gli incrementi atomici.
  • Questa variabile è locale? No.
  • E ‘il livello di istanza? Sì, dovrebbe essere parte di una singola istanza.

Voglio fare qualcosa del genere:

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

Da API di concorrenza di basso livello :

C’è una lunga lista di funzioni OSAtomicIncrement e OSAtomicDecrement che ti permettono di incrementare e decrementare un valore intero in modo atomico – thread-safe senza dover prendere un lock (o usare le code). Questi possono essere utili se è necessario incrementare i contatori globali da più thread per le statistiche. Se tutto ciò che fai è incrementare un contatore globale, le versioni OSAtomicIncrement senza barriere vanno bene, e quando non ci sono contese, sono economiche da chiamare.

Queste funzioni funzionano con numeri interi fissi, puoi scegliere la variante a 32 o 64 bit in base alle tue esigenze:

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

( Nota: come Erik Aigner ha notato correttamente, OSAtomicIncrement32 e gli amici sono deprecati a partire da macOS 10.12 / iOS 10.10 Xcode 8 suggerisce invece di utilizzare le funzioni da Tuttavia, ciò sembra essere difficile, confronta Swift 3: atomic_compare_exchange_strong e https : //openradar.appspot.com/27161329 Pertanto, il seguente approccio basato su GCD sembra essere la soluzione migliore ora.)

In alternativa, è ansible utilizzare una coda GCD per la sincronizzazione. Dalle code di invio nella “Guida alla programmazione della concorrenza”:

… Con le code di invio, è ansible aggiungere entrambe le attività a una coda di invio seriale per garantire che solo una delle attività abbia modificato la risorsa in un dato momento. Questo tipo di sincronizzazione basata sulla coda è più efficiente dei blocchi perché i lock richiedono sempre un costoso trap del kernel in entrambi i casi contestati e non contestati, mentre una coda di invio funziona principalmente nello spazio del processo dell’applicazione e invoca solo il kernel quando assolutamente necessario.

Nel tuo caso sarebbe

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

Vedere Aggiunta di elementi all’array Swift su più thread che causano problemi (perché gli array non sono thread-safe) – come faccio a evitarlo? o GCD con funzioni statiche di una struttura per esempi più sofisticati. Questo thread Quali vantaggi ha dispatch_sync su @synchronized? è anche molto interessante.

Le code sono un eccesso in questo caso. Puoi utilizzare un DispatchSemaphore introdotto in Swift 3 per questo scopo in questo modo:

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

L’ultima versione della class è disponibile qui .

So che questa domanda è già un po ‘più vecchia, ma di recente mi sono imbattuto nello stesso problema. Dopo aver studiato un po ‘e aver letto post come http://www.cocoawithlove.com/blog/2016/06/02/threads-and-mutexes.html, ho trovato questa soluzione per un contatore atomico. Forse aiuterà anche gli altri.

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

Dettagli

xCode 9.1, Swift 4

Soluzione

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

uso

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

Campione completo

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

risultati

inserisci la descrizione dell'immagine qui

Ho migliorato la risposta di @florian, utilizzando alcuni operatori sovraccaricati:

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