He buscado en el libro Swift, pero no puedo encontrar la versión Swift de @synchronized. ¿Cómo hago exclusión mutua en Swift?
removeFirst()
?
He buscado en el libro Swift, pero no puedo encontrar la versión Swift de @synchronized. ¿Cómo hago exclusión mutua en Swift?
removeFirst()
?
Respuestas:
Puedes usar GCD. Es un poco más detallado que @synchronized
, pero funciona como un reemplazo:
let serialQueue = DispatchQueue(label: "com.test.mySerialQueue")
serialQueue.sync {
// code
}
Estaba buscando esto yo mismo y llegué a la conclusión de que todavía no hay una construcción nativa dentro de Swift para esto.
Creé esta pequeña función auxiliar 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 una matriz como el argumento de bloqueo parece causar un error de compilación muy obtuso en este punto. De lo contrario, 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!
@synchronized
bloque muy bien, pero tenga en cuenta que no es idéntica a una declaración verdadero bloque incorporado como el @synchronized
bloque en Objective-C, porque return
y break
declaraciones trabajo ya no saltar de la función / bucle que rodea al igual lo haría si fuera una declaración ordinaria.
defer
palabra clave para garantizar que objc_sync_exit
se llame incluso si se closure
lanza.
Me gusta y utilizo muchas de las respuestas aquí, así que elegiría la que mejor funcione para usted. Dicho esto, el método que prefiero cuando necesito algo como el objetivo-c @synchronized
utiliza la defer
declaración 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 de contención de cualquier 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 transfiere el control del programa." 1
lock
? ¿Cómo se lock
inicializa?
lock
es cualquier objeto objetivo-c.
Puede intercalar declaraciones entre objc_sync_enter(obj: AnyObject?)
y objc_sync_exit(obj: AnyObject?)
. La palabra clave @synchronized está utilizando esos métodos debajo de las cubiertas. es decir
objc_sync_enter(self)
... synchronized code ...
objc_sync_exit(self)
objc_sync_enter
y objc_sync_exit
son métodos definidos en Objc-sync.h y son de código abierto: opensource.apple.com/source/objc4/objc4-371.2/runtime/…
objc_sync_enter(…)
y objc_sync_exit(…)
son encabezados públicos proporcionados por iOS / macOS / etc. API (parece que están dentro ….sdk
de la ruta usr/include/objc/objc-sync.h
) . La forma más fácil de averiguar si algo es una API pública o no es (en Xcode) escribir el nombre de la función (por ejemplo objc_sync_enter()
, no es necesario especificar argumentos para las funciones C) , luego intente hacer clic con el comando. Si le muestra el archivo de encabezado para esa API, entonces está bien (ya que no podría ver el encabezado si no fuera público) .
El análogo de la @synchronized
directiva de Objective-C puede tener un tipo de retorno arbitrario y un buen rethrows
comportamiento 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 defer
declaración permite devolver directamente un valor sin introducir una variable temporal.
En Swift 2 agregue el @noescape
atributo 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 retorno arbitrario) y Tod Cunningham [2] (donde me gusta defer
).
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 }
}
}
}
.serial
Parece no estar disponible. Pero .concurrent
está disponible. : /
myObject.state = myObject.state + 1
simultáneamente, no contaría las operaciones totales, sino que arrojaría un valor no determinista. Para resolver ese problema, el código de llamada debe estar envuelto en una cola en serie para que tanto la lectura como la escritura ocurran atómicamente. Por supuesto, Obj-c @synchronised
tiene el mismo problema, por lo que su implementación es correcta.
myObject.state += 1
es una combinación de una operación de lectura y luego de escritura. Algún otro hilo aún puede interponerse para establecer / escribir un valor. Según objc.io/blog/2018/12/18/atomic-variables , sería más fácil ejecutarlo set
en un bloque / cierre de sincronización y no bajo la variable misma.
Utilizando la respuesta de Bryan McLemore, la extendí para apoyar objetos que arrojan una mansión segura con la habilidad de aplazamiento Swift 2.0.
func synchronized( lock:AnyObject, block:() throws -> Void ) rethrows
{
objc_sync_enter(lock)
defer {
objc_sync_exit(lock)
}
try block()
}
rethrows
para simplificar el uso con cierres sin tirar (no es necesario usar try
), como se muestra en mi respuesta .
Para agregar la 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, puede 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
}
}
Swift 3
Este código tiene la capacidad de reingreso y puede funcionar con llamadas de función asincrónicas. En este código, después de llamar a someAsyncFunc (), se procesará otro cierre de función en la cola en serie, pero semaphore.wait () bloqueará hasta que se llame a signal (). 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 manejo de errores.
En la sesión 414 "Comprender los bloqueos y los registros de bloqueos" de la WWDC 2018, muestran la siguiente manera utilizando DispatchQueues con sincronización.
En swift 4 debería ser algo como lo siguiente:
class ImageCache {
private let queue = DispatchQueue(label: "sync queue")
private var storage: [String: UIImage] = [:]
public subscript(key: String) -> UIImage? {
get {
return queue.sync {
return storage[key]
}
}
set {
queue.sync {
storage[key] = newValue
}
}
}
}
De todos modos, también puede hacer lecturas más rápidas utilizando colas concurrentes con barreras. Las lecturas de sincronización y asíncrona se realizan simultáneamente y la escritura de un nuevo valor espera a que finalicen las operaciones anteriores.
class ImageCache {
private let queue = DispatchQueue(label: "with barriers", attributes: .concurrent)
private var storage: [String: UIImage] = [:]
func get(_ key: String) -> UIImage? {
return queue.sync { [weak self] in
guard let self = self else { return nil }
return self.storage[key]
}
}
func set(_ image: UIImage, for key: String) {
queue.async(flags: .barrier) { [weak self] in
guard let self = self else { return }
self.storage[key] = image
}
}
}
Use 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 hilos 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 provocar un comportamiento indefinido.
En el moderno Swift 5, con capacidad de retorno:
/**
Makes sure no other thread reenters the closure before the one running has not returned
*/
@discardableResult
public func synchronized<T>(_ lock: AnyObject, closure:() -> T) -> T {
objc_sync_enter(lock)
defer { objc_sync_exit(lock) }
return closure()
}
Úselo así, para aprovechar la capacidad de valor de retorno:
let returnedValue = synchronized(self) {
// Your code here
return yourCode()
}
O así de otra manera:
synchronized(self) {
// Your code here
yourCode()
}
GCD
). Parece que esencialmente nadie usa o entiende cómo usar Thread
. Estoy muy contento con eso, mientras que GCD
está lleno de problemas y limitaciones.
Prueba: NSRecursiveLock
Un bloqueo que puede ser adquirido varias veces por el mismo hilo sin causar un punto muerto.
let lock = NSRecursiveLock()
func f() {
lock.lock()
//Your Code
lock.unlock()
}
func f2() {
lock.lock()
defer {
lock.unlock()
}
//Your Code
}
Figura Publicaré mi implementación de Swift 5, basada en las respuestas anteriores. ¡Gracias chicos! Me pareció útil tener uno que también devuelva un valor, así que tengo dos métodos.
Aquí hay una clase simple para hacer primero:
import Foundation
class Sync {
public class func synced(_ lock: Any, closure: () -> ()) {
objc_sync_enter(lock)
defer { objc_sync_exit(lock) }
closure()
}
public class func syncedReturn(_ lock: Any, closure: () -> (Any?)) -> Any? {
objc_sync_enter(lock)
defer { objc_sync_exit(lock) }
return closure()
}
}
Luego úselo así si necesita un valor de retorno:
return Sync.syncedReturn(self, closure: {
// some code here
return "hello world"
})
O:
Sync.synced(self, closure: {
// do some work synchronously
})
public class func synced<T>(_ lock: Any, closure: () -> T)
, funciona para ambos, nulo y cualquier otro tipo. También está el material de rebrote.
xCode 8.3.1, swift 3.1
Leer el valor de escritura de diferentes hilos (asíncrono).
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)"
}
}
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
}
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)")
}
}
}
Con los envoltorios de propiedades de Swift, esto es lo que estoy usando ahora:
@propertyWrapper public struct NCCSerialized<Wrapped> {
private let queue = DispatchQueue(label: "com.nuclearcyborg.NCCSerialized_\(UUID().uuidString)")
private var _wrappedValue: Wrapped
public var wrappedValue: Wrapped {
get { queue.sync { _wrappedValue } }
set { queue.sync { _wrappedValue = newValue } }
}
public init(wrappedValue: Wrapped) {
self._wrappedValue = wrappedValue
}
}
Entonces puedes simplemente hacer:
@NCCSerialized var foo: Int = 10
o
@NCCSerialized var myData: [SomeStruct] = []
Luego acceda a la variable como lo haría normalmente.
DispatchQueue
que está oculto para el usuario. Encontré esta referencia SO para tranquilizarme: stackoverflow.com/a/35022486/1060314
En conclusión, aquí damos una forma más común que incluye el valor de retorno o nulo, y arrojamos
import Foundation
extension NSObject {
func synchronized<T>(lockObj: AnyObject!, closure: () throws -> T) rethrows -> T
{
objc_sync_enter(lockObj)
defer {
objc_sync_exit(lockObj)
}
return try closure()
}
}
¿Por qué hacerlo difícil y molesto con las cerraduras? Utilice barreras de envío.
Una barrera de despacho crea un punto de sincronización dentro de una cola concurrente.
Mientras se está ejecutando, no se permite ejecutar ningún otro bloque en la cola, incluso si es concurrente y hay otros núcleos disponibles.
Si eso suena como un bloqueo exclusivo (escritura), lo es. Los bloques sin barrera pueden considerarse 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.
Basado en ɳeuroburɳ , pruebe 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()
1
2
3
11
22
33
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
}
}