concurrency mutex swift

concurrency - ¿Cuál es el equivalente de Swift a “@synchronized” de Objective-C?



mutex (17)

He buscado en el libro Swift, pero no puedo encontrar la versión Swift de @synchronized. ¿Cómo hago la exclusión mutua en Swift?


Detalles

xCode 8.3.1, swift 3.1

Tarea

Leer valor de escritura desde diferentes hilos (async).

Código

class AsyncObject<T>:CustomStringConvertible { private var _value: T public private(set) var dispatchQueueName: String let dispatchQueue: DispatchQueue init (value: T, dispatchQueueName: String) { _value = value self.dispatchQueueName = dispatchQueueName dispatchQueue = DispatchQueue(label: dispatchQueueName) } func setValue(with closure: @escaping (_ currentValue: T)->(T) ) { dispatchQueue.sync { [weak self] in if let _self = self { _self._value = closure(_self._value) } } } func getValue(with closure: @escaping (_ currentValue: T)->() ) { dispatchQueue.sync { [weak self] in if let _self = self { closure(_self._value) } } } var value: T { get { return dispatchQueue.sync { _value } } set (newValue) { dispatchQueue.sync { _value = newValue } } } var description: String { return "/(_value)" } }

Uso

print("Single read/write action") // Use it when when you need to make single action let obj = AsyncObject<Int>(value: 0, dispatchQueueName: "Dispatch0") obj.value = 100 let x = obj.value print(x) print("Write action in block") // Use it when when you need to make many action obj.setValue{ (current) -> (Int) in let newValue = current*2 print("previous: /(current), new: /(newValue)") return newValue }

Muestra completa

extensión DispatchGroup

extension DispatchGroup { class func loop(repeatNumber: Int, action: @escaping (_ index: Int)->(), completion: @escaping ()->()) { let group = DispatchGroup() for index in 0...repeatNumber { group.enter() DispatchQueue.global(qos: .utility).async { action(index) group.leave() } } group.notify(queue: DispatchQueue.global(qos: .userInitiated)) { completion() } } }

clase ViewController

import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() //sample1() sample2() } func sample1() { print("=================================================/nsample with variable") let obj = AsyncObject<Int>(value: 0, dispatchQueueName: "Dispatch1") DispatchGroup.loop(repeatNumber: 5, action: { index in obj.value = index }) { print("/(obj.value)") } } func sample2() { print("/n=================================================/nsample with array") let arr = AsyncObject<[Int]>(value: [], dispatchQueueName: "Dispatch2") DispatchGroup.loop(repeatNumber: 15, action: { index in arr.setValue{ (current) -> ([Int]) in var array = current array.append(index*index) print("index: /(index), value /(array[array.count-1])") return array } }) { print("/(arr.value)") } } }


¿Por qué hacerlo difícil y la molestia con las cerraduras? Utilice barreras de despacho.

Una barrera de despacho crea un punto de sincronización dentro de una cola concurrente.

Mientras se está ejecutando, no se permite que se ejecute ningún otro bloque en la cola, incluso si es concurrente y otros núcleos están disponibles.

Si eso suena como un bloqueo exclusivo (escritura), lo es. Los bloques sin barrera pueden considerarse como bloqueos compartidos (leídos).

Siempre que todo el acceso al recurso se realice a través de la cola, las barreras proporcionan una sincronización muy barata.


Acabo de encontrar la respuesta en la sesión 414 de la WWDC de 2018, "Entendiendo los accidentes y los registros de accidentes". Como ha señalado Comulligan, la forma correcta debería ser utilizar DispatchQueues con sync.

En swift 4 debería ser algo como lo siguiente:

class ImageCache { private let queue = DispatchQueue(label: "com.company.name.cache") private var storage: [String: UIImage] = [:] public subscript(key: String) -> UIImage? { get { return queue.sync { return storage[key] } } set { queue.sync { storage[key] = newValue } } } }


El análogo de la directiva @synchronized de Objective-C puede tener un tipo de retorno arbitrario y un buen comportamiento de rethrows en Swift.

// Swift 3 func synchronized<T>(_ lock: AnyObject, _ body: () throws -> T) rethrows -> T { objc_sync_enter(lock) defer { objc_sync_exit(lock) } return try body() }

El uso de la instrucción de defer permite devolver directamente un valor sin introducir una variable temporal.

En Swift 2, agregue el atributo @noescape al cierre para permitir más optimizaciones:

// Swift 2 func synchronized<T>(lock: AnyObject, @noescape _ body: () throws -> T) rethrows -> T { objc_sync_enter(lock) defer { objc_sync_exit(lock) } return try body() }

Basado en las respuestas de GNewc [1] (donde me gusta el tipo de devolución arbitraria) y Tod Cunningham [2] (donde me gusta defer ).


En conclusión, aquí se da una forma más común que incluye valor de retorno o nulo, y lanzar

Fundación de importación

extension NSObject { func synchronized<T>(lockObj: AnyObject!, closure: () throws -> T) rethrows -> T { objc_sync_enter(lockObj) defer { objc_sync_exit(lockObj) } return try closure() } }


Lo busqué yo mismo y llegué a la conclusión de que todavía no existe una construcción nativa dentro de swift para esto.

Hice esta pequeña función de ayuda basada en algunos de los códigos que he visto de Matt Bridges y otros.

func synced(_ lock: Any, closure: () -> ()) { objc_sync_enter(lock) closure() objc_sync_exit(lock) }

El uso es bastante sencillo

synced(self) { println("This is a synchronized closure") }

Hay un problema que he encontrado con esto. Pasar en una matriz como el argumento de bloqueo parece causar un error de compilación muy obtuso en este punto. De lo contrario, aunque parece funcionar como se desea.

Bitcast requires both operands to be pointer or neither %26 = bitcast i64 %25 to %objc_object*, !dbg !378 LLVM ERROR: Broken function found, compilation aborted!


Me gustan y utilizo muchas de las respuestas aquí, así que elegiré la que mejor funcione para usted. Dicho esto, el método que prefiero cuando necesito algo como @synchronized de @synchronized -c usa la instrucción de @synchronized introducida en swift 2.

{ objc_sync_enter(lock) defer { objc_sync_exit(lock) } // // code of critical section goes here // } // <-- lock released when this block is exited

Lo bueno de este método es que su sección crítica puede salir del bloque contenedor de la manera deseada (por ejemplo, return , break , continue , throw ) y "las declaraciones dentro de la declaración de aplazamiento se ejecutan sin importar cómo se transfiera el control del programa. . " defer


Otro método es crear una superclase y luego heredarla. De esta manera puedes usar GCD más directamente

class Lockable { let lockableQ:dispatch_queue_t init() { lockableQ = dispatch_queue_create("com.blah.blah./(self.dynamicType)", DISPATCH_QUEUE_SERIAL) } func lock(closure: () -> ()) { dispatch_sync(lockableQ, closure) } } class Foo: Lockable { func boo() { lock { ....... do something } }


Para agregar funcionalidad de retorno, puede hacer esto:

func synchronize<T>(lockObj: AnyObject!, closure: ()->T) -> T { objc_sync_enter(lockObj) var retVal: T = closure() objc_sync_exit(lockObj) return retVal }

Posteriormente, puedes llamarlo usando:

func importantMethod(...) -> Bool { return synchronize(self) { if(feelLikeReturningTrue) { return true } // do other things if(feelLikeReturningTrueNow) { return true } // more things return whatIFeelLike ? true : false } }


Puede objc_sync_enter(obj: AnyObject?) declaraciones entre objc_sync_enter(obj: AnyObject?) Y objc_sync_exit(obj: AnyObject?) . La palabra clave @synchronized está usando esos métodos debajo de las cubiertas. es decir

objc_sync_enter(self) ... synchronized code ... objc_sync_exit(self)


Usando la respuesta de Bryan McLemore, la extendí para admitir objetos que arrojan una casa segura con la capacidad de aplazamiento de Swift 2.0.

func synchronized( lock:AnyObject, block:() throws -> Void ) rethrows { objc_sync_enter(lock) defer { objc_sync_exit(lock) } try block() }


Utilice NSLock en Swift4:

let lock = NSLock() lock.lock() if isRunning == true { print("Service IS running ==> please wait") return } else { print("Service not running") } isRunning = true lock.unlock()

Advertencia La clase NSLock usa subprocesos POSIX para implementar su comportamiento de bloqueo. Al enviar un mensaje de desbloqueo a un objeto NSLock, debe asegurarse de que el mensaje se envíe desde el mismo hilo que envió el mensaje de bloqueo inicial. Desbloquear un bloqueo de un hilo diferente puede resultar en un comportamiento indefinido.


Utilice GCD. Es un poco más detallado que @synchronized , pero funciona perfectamente como reemplazo:

let serialQueue = DispatchQueue(label: "com.test.mySerialQueue") serialQueue.sync { // code }


dispatch_barrier_async es la mejor manera, sin bloquear el hilo actual.

dispatch_barrier_async (accessQueue, {dictionary [object.ID] = object})


SWIFT 4

En Swift 4 puede usar las colas de despacho de GCD para bloquear recursos.

class MyObject { private var internalState: Int = 0 private let internalQueue: DispatchQueue = DispatchQueue(label:"LockingQueue") // Serial by default var state: Int { get { return internalQueue.sync { internalState } } set (newState) { internalQueue.sync { internalState = newState } } } }


Swift 3

Este código tiene la capacidad de reingreso y puede funcionar con llamadas de función asíncronas. En este código, después de llamar a algunos AssyncFunc (), otra función de cierre en la cola serie se procesará pero será bloqueada por semáforo.wait () hasta que se llame a la señal (). internalQueue.sync no debe usarse, ya que bloqueará el hilo principal si no me equivoco.

let internalQueue = DispatchQueue(label: "serialQueue") let semaphore = DispatchSemaphore(value: 1) internalQueue.async { self.semaphore.wait() // Critical section someAsyncFunc() { // Do some work here self.semaphore.signal() } }

objc_sync_enter / objc_sync_exit no es una buena idea sin el manejo de errores.


Basado en ɲeuroburɳ , prueba un caso de subclase

class Foo: NSObject { func test() { print("1") objc_sync_enter(self) defer { objc_sync_exit(self) print("3") } print("2") } } class Foo2: Foo { override func test() { super.test() print("11") objc_sync_enter(self) defer { print("33") objc_sync_exit(self) } print("22") } } let test = Foo2() test.test()

Salida:

1 2 3 11 22 33