Ho cercato nel libro Swift, ma non riesco a trovare la versione Swift di @synchronized. Come posso fare l'esclusione reciproca in Swift?
removeFirst()
?
Ho cercato nel libro Swift, ma non riesco a trovare la versione Swift di @synchronized. Come posso fare l'esclusione reciproca in Swift?
removeFirst()
?
Risposte:
Puoi usare GCD. È un po 'più dettagliato di @synchronized
, ma funziona come un sostituto:
let serialQueue = DispatchQueue(label: "com.test.mySerialQueue")
serialQueue.sync {
// code
}
Lo stavo cercando da solo e sono giunto alla conclusione che non esiste ancora un costrutto nativo all'interno di rapido per questo.
Ho creato questa piccola funzione di supporto basata su alcuni dei codici che ho visto da Matt Bridges e altri.
func synced(_ lock: Any, closure: () -> ()) {
objc_sync_enter(lock)
closure()
objc_sync_exit(lock)
}
L'utilizzo è piuttosto semplice
synced(self) {
println("This is a synchronized closure")
}
C'è un problema che ho riscontrato con questo. Passare in un array come argomento lock sembra causare un errore del compilatore molto ottuso a questo punto. Altrimenti però sembra funzionare come desiderato.
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
blocco, ma nota che non è identico a un'istruzione di blocco reale incorporata come il @synchronized
blocco in Objective-C, perché le istruzioni return
e break
non funzionano più per saltare fuori dalla funzione / loop circostante come sarebbe se questa fosse un'affermazione normale.
defer
parola chiave per assicurarsi che objc_sync_exit
venga chiamato anche se closure
genera.
Mi piacciono e uso molte delle risposte qui, quindi sceglierei quale funziona meglio per te. Detto questo, il metodo che preferisco quando ho bisogno di qualcosa di simile a object-c @synchronized
usa l' defer
istruzione introdotta in swift 2.
{
objc_sync_enter(lock)
defer { objc_sync_exit(lock) }
//
// code of critical section goes here
//
} // <-- lock released when this block is exited
La cosa bella di questo metodo, è che la vostra sezione critica può uscire dal blocco che contiene in alcun modo desiderato (ad esempio, return
, break
, continue
, throw
), e "le dichiarazioni all'interno dell'istruzione Defer vengono eseguite non importa come viene trasferito il controllo del programma." 1
lock
? Come viene lock
inizializzato?
lock
è un qualsiasi oggetto-c.
È possibile inserire istruzioni tra objc_sync_enter(obj: AnyObject?)
e objc_sync_exit(obj: AnyObject?)
. La parola chiave @synchronized sta usando quei metodi sotto le copertine. vale a dire
objc_sync_enter(self)
... synchronized code ...
objc_sync_exit(self)
objc_sync_enter
e objc_sync_exit
sono metodi definiti in Objc-sync.h e sono open source: opensource.apple.com/source/objc4/objc4-371.2/runtime/…
objc_sync_enter(…)
e objc_sync_exit(…)
sono le intestazioni pubbliche fornite da iOS / macOS / ecc. API (sembra che siano all'interno ….sdk
del percorso usr/include/objc/objc-sync.h
) . Il modo più semplice per scoprire se qualcosa è un'API pubblica o no è digitare (in Xcode) il nome della funzione (ad es objc_sync_enter()
. Non è necessario specificare gli argomenti per le funzioni C) , quindi provare a fare clic su di esso. Se ti mostra il file di intestazione per quell'API, allora sei bravo (dal momento che non saresti in grado di vedere l'intestazione se non fosse pubblica) .
L'analogo della @synchronized
direttiva di Objective-C può avere un tipo di ritorno arbitrario e un rethrows
comportamento piacevole in 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()
}
L'uso defer
dell'istruzione consente di restituire direttamente un valore senza introdurre una variabile temporanea.
In Swift 2 aggiungi l' @noescape
attributo alla chiusura per consentire ulteriori ottimizzazioni:
// 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()
}
Basato sulle risposte di GNewc [1] (dove mi piace il tipo di ritorno arbitrario) e Tod Cunningham [2] (dove mi piace defer
).
SWIFT 4
In Swift 4 è possibile utilizzare le code di invio GCD per bloccare le risorse.
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
sembra non essere disponibile. Ma .concurrent
è disponibile : /
myObject.state = myObject.state + 1
contemporaneamente, non conterebbe le operazioni totali ma produrrebbe invece un valore non deterministico. Per risolvere questo problema, il codice chiamante dovrebbe essere racchiuso in una coda seriale in modo che sia la lettura che la scrittura avvengano atomicamente. Ovviamente Obj-c @synchronised
ha lo stesso problema, quindi in questo senso la tua implementazione è corretta.
myObject.state += 1
è una combinazione di un'operazione di lettura e quindi di scrittura. Alcuni altri thread possono ancora trovarsi in mezzo per impostare / scrivere un valore. Secondo objc.io/blog/2018/12/18/atomic-variables , sarebbe invece più semplice eseguire set
in un blocco / chiusura di sincronizzazione e non sotto la variabile stessa.
Usando la risposta di Bryan McLemore, l'ho estesa per supportare oggetti che gettano in un maniero sicuro con l'abilità di differimento di Swift 2.0.
func synchronized( lock:AnyObject, block:() throws -> Void ) rethrows
{
objc_sync_enter(lock)
defer {
objc_sync_exit(lock)
}
try block()
}
rethrows
per semplificare l'uso con chiusure non gettanti (non c'è bisogno di usare try
), come mostrato nella mia risposta .
Per aggiungere la funzionalità di ritorno, è possibile effettuare ciò:
func synchronize<T>(lockObj: AnyObject!, closure: ()->T) -> T
{
objc_sync_enter(lockObj)
var retVal: T = closure()
objc_sync_exit(lockObj)
return retVal
}
Successivamente, puoi chiamarlo 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
Questo codice ha la possibilità di rientrare e può funzionare con chiamate di funzione asincrone. In questo codice, dopo che viene chiamato someAsyncFunc (), verrà elaborata un'altra chiusura della funzione sulla coda seriale, ma verrà bloccata da semaphore.wait () fino alla chiamata di signal (). internalQueue.sync non dovrebbe essere usato in quanto bloccherebbe il thread principale se non sbaglio.
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 non è una buona idea senza la gestione degli errori.
Nella sessione "Informazioni sugli arresti anomali e sui registri degli arresti anomali" 414 del WWDC 2018 mostrano il seguente modo usando DispatchQueues con sincronizzazione.
In swift 4 dovrebbe essere qualcosa di simile al seguente:
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
}
}
}
}
Ad ogni modo è anche possibile effettuare letture più velocemente utilizzando code simultanee con barriere. Le letture di sincronizzazione e asincrono vengono eseguite contemporaneamente e la scrittura di un nuovo valore attende il completamento delle operazioni precedenti.
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
}
}
}
Usa NSLock in 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()
Avviso La classe NSLock utilizza thread POSIX per implementare il suo comportamento di blocco. Quando si invia un messaggio di sblocco a un oggetto NSLock, è necessario assicurarsi che il messaggio venga inviato dallo stesso thread che ha inviato il messaggio di blocco iniziale. Lo sblocco di un blocco da un thread diverso può comportare un comportamento indefinito.
Nella moderna Swift 5, con funzionalità di ritorno:
/**
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()
}
Usalo in questo modo, per sfruttare la capacità del valore di ritorno:
let returnedValue = synchronized(self) {
// Your code here
return yourCode()
}
O così altrimenti:
synchronized(self) {
// Your code here
yourCode()
}
GCD
). Sembra che praticamente nessuno usi o capisca come usare Thread
. Ne sono contento, mentre GCD
è pieno di problemi e limitazioni.
Prova: NSRecursiveLock
Un blocco che può essere acquisito più volte dallo stesso thread senza causare un deadlock.
let lock = NSRecursiveLock()
func f() {
lock.lock()
//Your Code
lock.unlock()
}
func f2() {
lock.lock()
defer {
lock.unlock()
}
//Your Code
}
Figura posterò la mia implementazione di Swift 5, basata sulle risposte precedenti. Grazie ragazzi! Ho trovato utile averne uno che restituisca anche un valore, quindi ho due metodi.
Ecco una semplice classe da fare per prima:
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()
}
}
Quindi usalo in questo modo se hai bisogno di un valore di ritorno:
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)
, funziona sia per il vuoto che per qualsiasi altro tipo. C'è anche roba da ricrescita.
Codice x 8.3.1, rapido 3.1
Leggi il valore di scrittura da diversi thread (asincrono).
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
}
estensione 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()
}
}
}
classe 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 i wrapper di proprietà di Swift, questo è quello che sto usando ora:
@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
}
}
Quindi puoi semplicemente fare:
@NCCSerialized var foo: Int = 10
o
@NCCSerialized var myData: [SomeStruct] = []
Quindi accedi alla variabile come faresti normalmente.
DispatchQueue
oggetto nascosto all'utente. Ho trovato questo riferimento SO per mettermi a mio agio: stackoverflow.com/a/35022486/1060314
In conclusione, Here fornisce un modo più comune che include valore di ritorno o vuoto e lancio
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()
}
}
Perché renderlo difficile e seccante con le serrature? Usa le barriere di spedizione.
Una barriera di invio crea un punto di sincronizzazione all'interno di una coda simultanea.
Mentre è in esecuzione, nessun altro blocco sulla coda è autorizzato a funzionare, anche se è simultaneo e sono disponibili altri core.
Se suona come un blocco esclusivo (scrittura), lo è. I blocchi non barriera possono essere pensati come blocchi condivisi (di lettura).
Finché tutto l'accesso alla risorsa viene eseguito attraverso la coda, le barriere forniscono una sincronizzazione molto economica.
Basato su ɲeuroburɳ , prova un caso di sottoclasse
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
Un altro metodo consiste nel creare una superclasse e quindi ereditarla. In questo modo puoi utilizzare GCD più direttamente
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
}
}