Ich habe das Swift-Buch durchsucht, kann aber die Swift-Version von @synchronized nicht finden. Wie kann ich mich in Swift gegenseitig ausschließen?
removeFirst()
?
Ich habe das Swift-Buch durchsucht, kann aber die Swift-Version von @synchronized nicht finden. Wie kann ich mich in Swift gegenseitig ausschließen?
removeFirst()
?
Antworten:
Sie können GCD verwenden. Es ist etwas ausführlicher als @synchronized
, funktioniert aber als Ersatz:
let serialQueue = DispatchQueue(label: "com.test.mySerialQueue")
serialQueue.sync {
// code
}
Ich habe selbst danach gesucht und bin zu dem Schluss gekommen, dass es dafür noch kein natives Konstrukt innerhalb von Swift gibt.
Ich habe diese kleine Hilfsfunktion basierend auf dem Code erfunden, den ich von Matt Bridges und anderen gesehen habe.
func synced(_ lock: Any, closure: () -> ()) {
objc_sync_enter(lock)
closure()
objc_sync_exit(lock)
}
Die Verwendung ist ziemlich einfach
synced(self) {
println("This is a synchronized closure")
}
Es gibt ein Problem, das ich damit gefunden habe. Die Übergabe eines Arrays als Sperrargument scheint an dieser Stelle einen sehr stumpfen Compilerfehler zu verursachen. Ansonsten scheint es aber wie gewünscht zu funktionieren.
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
Blocks gut, aber beachten Sie, dass sie nicht mit einer echten eingebauten Blockanweisung wie dem @synchronized
Block in Objective-C identisch ist , da return
und break
Anweisungen nicht mehr funktionieren, um aus der umgebenden Funktion / Schleife herauszuspringen es wäre, wenn dies eine gewöhnliche Aussage wäre.
defer
Schlüsselwort zu verwenden, um sicherzustellen objc_sync_exit
, dass es auch bei closure
Würfen aufgerufen wird .
Ich mag und verwende viele der Antworten hier, also würde ich wählen, welche für Sie am besten funktioniert. Die Methode, die ich bevorzuge, wenn ich so etwas wie Objective-Cs benötige, @synchronized
verwendet die defer
in Swift 2 eingeführte Aussage.
{
objc_sync_enter(lock)
defer { objc_sync_exit(lock) }
//
// code of critical section goes here
//
} // <-- lock released when this block is exited
Das Schöne an dieser Methode ist , dass Ihr kritischer Abschnitt den umschließende Block in jeder gewünschten Weise verlassen kann (zB return
, break
, continue
, throw
) und „die Aussagen in der Zurückstellungs Anweisung unabhängig davon ausgeführt , wie die Programmsteuerung übertragen wird.“ 1
lock
? Wie wird lock
initialisiert?
lock
ist ein objektives Objekt.
Sie können Anweisungen zwischen objc_sync_enter(obj: AnyObject?)
und einfügen objc_sync_exit(obj: AnyObject?)
. Das Schlüsselwort @synchronized verwendet diese Methoden unter dem Deckmantel. dh
objc_sync_enter(self)
... synchronized code ...
objc_sync_exit(self)
objc_sync_enter
und objc_sync_exit
sind Methoden, die in Objc-sync.h definiert sind und Open Source sind: opensource.apple.com/source/objc4/objc4-371.2/runtime/…
objc_sync_enter(…)
& objc_sync_exit(…)
sind öffentliche Header, die von iOS / macOS / etc. APIs (sieht aus, als ob sie sich ….sdk
im Pfad befinden usr/include/objc/objc-sync.h
) . Der einfachste Weg, um herauszufinden, ob etwas eine öffentliche API ist oder nicht, besteht darin, (in Xcode) den Funktionsnamen einzugeben (z objc_sync_enter()
. B. müssen für C-Funktionen keine Argumente angegeben werden) und dann mit der Befehlstaste darauf zu klicken. Wenn es Ihnen die Header-Datei für diese API zeigt, sind Sie gut (da Sie den Header nicht sehen könnten, wenn er nicht öffentlich wäre) .
Analog zur @synchronized
Direktive von Objective-C kann es rethrows
in Swift einen beliebigen Rückgabetyp und ein nettes Verhalten geben.
// Swift 3
func synchronized<T>(_ lock: AnyObject, _ body: () throws -> T) rethrows -> T {
objc_sync_enter(lock)
defer { objc_sync_exit(lock) }
return try body()
}
Durch die Verwendung der defer
Anweisung können Sie einen Wert direkt zurückgeben, ohne eine temporäre Variable einzuführen.
Fügen Sie in Swift 2 das @noescape
Attribut zum Abschluss hinzu, um weitere Optimierungen zu ermöglichen:
// 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()
}
Basierend auf den Antworten von GNewc [1] (wo ich einen beliebigen Rückgabetyp mag) und Tod Cunningham [2] (wo ich mag defer
).
SWIFT 4
In Swift 4 können Sie GCDs-Versandwarteschlangen verwenden, um Ressourcen zu sperren.
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
scheint nicht verfügbar zu sein. Ist .concurrent
aber vorhanden. : /
myObject.state = myObject.state + 1
gleichzeitig ausgeführt werden, werden nicht die Gesamtoperationen gezählt, sondern ein nicht deterministischer Wert ausgegeben. Um dieses Problem zu lösen, sollte der aufrufende Code in eine serielle Warteschlange eingeschlossen werden, damit sowohl das Lesen als auch das Schreiben atomar erfolgen. Natürlich hat Obj-c's @synchronised
das gleiche Problem, also ist Ihre Implementierung in diesem Sinne korrekt.
myObject.state += 1
ist eine Kombination aus einer Lese- und einer Schreiboperation. Dazwischen kann noch ein anderer Thread stehen, um einen Wert zu setzen / zu schreiben. Wie pro objc.io/blog/2018/12/18/atomic-variables , wäre es einfacher, die läuft set
in einem Synchronisationsblock / Schließung statt und nicht unter der Variable selbst.
Um die Rückgabefunktionalität hinzuzufügen, können Sie Folgendes tun:
func synchronize<T>(lockObj: AnyObject!, closure: ()->T) -> T
{
objc_sync_enter(lockObj)
var retVal: T = closure()
objc_sync_exit(lockObj)
return retVal
}
Anschließend können Sie es aufrufen mit:
func importantMethod(...) -> Bool {
return synchronize(self) {
if(feelLikeReturningTrue) { return true }
// do other things
if(feelLikeReturningTrueNow) { return true }
// more things
return whatIFeelLike ? true : false
}
}
Mit der Antwort von Bryan McLemore habe ich sie erweitert, um Objekte zu unterstützen, die ein sicheres Herrenhaus mit der Swift 2.0-Aufschubfähigkeit bieten.
func synchronized( lock:AnyObject, block:() throws -> Void ) rethrows
{
objc_sync_enter(lock)
defer {
objc_sync_exit(lock)
}
try block()
}
rethrows
die Verwendung mit nicht werfenden Verschlüssen zu vereinfachen (keine Verwendung erforderlich try
), wie in meiner Antwort gezeigt .
Swift 3
Dieser Code kann erneut eingegeben werden und kann mit asynchronen Funktionsaufrufen arbeiten. In diesem Code wird nach dem Aufruf von someAsyncFunc () ein weiterer Funktionsabschluss in der seriellen Warteschlange verarbeitet, jedoch von semaphore.wait () blockiert, bis signal () aufgerufen wird. internalQueue.sync sollte nicht verwendet werden, da es den Hauptthread blockiert, wenn ich mich nicht irre.
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 ist ohne Fehlerbehandlung keine gute Idee.
In der Sitzung 414 "Grundlegendes zu Abstürzen und Absturzprotokollen" des WWDC 2018 wird die folgende Methode zur Verwendung von DispatchQueues mit Synchronisierung gezeigt.
In Swift 4 sollte so etwas wie das Folgende sein:
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
}
}
}
}
Auf jeden Fall können Sie Lesevorgänge auch durch gleichzeitige Warteschlangen mit Barrieren beschleunigen. Synchronisierungs- und Asynchronisierungslesevorgänge werden gleichzeitig ausgeführt, und das Schreiben eines neuen Werts wartet, bis die vorherigen Vorgänge abgeschlossen sind.
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
}
}
}
Verwenden Sie 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()
Warnung Die NSLock-Klasse verwendet POSIX-Threads, um ihr Sperrverhalten zu implementieren. Wenn Sie eine Entsperrnachricht an ein NSLock-Objekt senden, müssen Sie sicherstellen, dass die Nachricht von demselben Thread gesendet wird, der die erste Sperrnachricht gesendet hat. Das Entsperren einer Sperre aus einem anderen Thread kann zu undefiniertem Verhalten führen.
Im modernen Swift 5 mit Rückgabefähigkeit:
/**
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()
}
Verwenden Sie es wie folgt, um die Rückgabewertfunktion zu nutzen:
let returnedValue = synchronized(self) {
// Your code here
return yourCode()
}
Oder anders anders:
synchronized(self) {
// Your code here
yourCode()
}
GCD
). Es scheint, dass im Wesentlichen niemand etwas benutzt oder versteht, wie man es benutzt Thread
. Ich bin sehr zufrieden damit - während GCD
es mit Fallstricken und Einschränkungen behaftet ist.
Versuchen Sie: NSRecursiveLock
Eine Sperre, die von demselben Thread mehrmals erfasst werden kann, ohne einen Deadlock zu verursachen.
let lock = NSRecursiveLock()
func f() {
lock.lock()
//Your Code
lock.unlock()
}
func f2() {
lock.lock()
defer {
lock.unlock()
}
//Your Code
}
Abbildung Ich werde meine Swift 5-Implementierung veröffentlichen, die auf den vorherigen Antworten basiert. Danke Leute! Ich fand es hilfreich, eine zu haben, die auch einen Wert zurückgibt, also habe ich zwei Methoden.
Hier ist eine einfache Klasse, die zuerst erstellt werden muss:
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()
}
}
Verwenden Sie es dann wie folgt, wenn Sie einen Rückgabewert benötigen:
return Sync.syncedReturn(self, closure: {
// some code here
return "hello world"
})
Oder:
Sync.synced(self, closure: {
// do some work synchronously
})
public class func synced<T>(_ lock: Any, closure: () -> T)
, funktioniert sowohl für void als auch für jeden anderen Typ. Es gibt auch das Nachwachsen Zeug.
xCode 8.3.1, schnell 3.1
Schreibwert von verschiedenen Threads lesen (asynchron).
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
}
Erweiterung 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()
}
}
}
Klasse 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)")
}
}
}
Mit Swifts Property Wrappern verwende ich Folgendes:
@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
}
}
Dann können Sie einfach tun:
@NCCSerialized var foo: Int = 10
oder
@NCCSerialized var myData: [SomeStruct] = []
Greifen Sie dann wie gewohnt auf die Variable zu.
DispatchQueue
die dem Benutzer verborgen bleibt. Ich fand diese SO-Referenz, um mich zu beruhigen
Abschließend geben Sie hier einen allgemeineren Weg an, der Rückgabewert oder void enthält, und werfen
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()
}
}
Warum es schwierig machen und mit Schlössern Ärger machen? Verwenden Sie Versandbarrieren.
Eine Versandbarriere erstellt einen Synchronisationspunkt innerhalb einer gleichzeitigen Warteschlange.
Während der Ausführung darf kein anderer Block in der Warteschlange ausgeführt werden, selbst wenn er gleichzeitig ausgeführt wird und andere Kerne verfügbar sind.
Wenn das nach einer exklusiven (Schreib-) Sperre klingt, ist es das auch. Nicht-Barriere-Blöcke können als gemeinsam genutzte (Lese-) Sperren betrachtet werden.
Solange der gesamte Zugriff auf die Ressource über die Warteschlange erfolgt, bieten Barrieren eine sehr kostengünstige Synchronisation.
Testen Sie anhand von „Neurobur“ einen Fall der Unterklasse
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
Eine andere Methode besteht darin, eine Oberklasse zu erstellen und diese dann zu erben. Auf diese Weise können Sie GCD direkter verwenden
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
}
}