Przeszukałem książkę Swift, ale nie mogę znaleźć wersji Swift @synchronized. Jak mogę dokonać wzajemnego wykluczenia w Swift?
removeFirst()
?
Przeszukałem książkę Swift, ale nie mogę znaleźć wersji Swift @synchronized. Jak mogę dokonać wzajemnego wykluczenia w Swift?
removeFirst()
?
Odpowiedzi:
Możesz użyć GCD. Jest trochę bardziej szczegółowy @synchronized
, ale działa jako zamiennik:
let serialQueue = DispatchQueue(label: "com.test.mySerialQueue")
serialQueue.sync {
// code
}
Szukałem tego sam i doszedłem do wniosku, że nie ma jeszcze natywnej konstrukcji w swift.
Zrobiłem tę małą funkcję pomocnika na podstawie kodu, który widziałem od Matta Bridgesa i innych.
func synced(_ lock: Any, closure: () -> ()) {
objc_sync_enter(lock)
closure()
objc_sync_exit(lock)
}
Użycie jest dość proste
synced(self) {
println("This is a synchronized closure")
}
Znalazłem z tym jeden problem. Przekazywanie tablicy jako argumentu blokady wydaje się w tym momencie powodować bardzo tępy błąd kompilatora. W przeciwnym razie wydaje się, że działa zgodnie z oczekiwaniami.
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
ładnie zachowuje składnię bloku, ale należy pamiętać, że nie jest on identyczny z rzeczywistą wbudowaną instrukcją bloku, taką jak @synchronized
blok w Objective-C, ponieważ instrukcje return
i break
nie działają już tak, aby wyskakiwały z otaczającej funkcji / pętli jak byłoby, gdyby było to zwykłe stwierdzenie.
defer
słowa kluczowego, aby zapewnić, że objc_sync_exit
zostanie wywołany, nawet jeśli closure
wyrzuca.
Podobają mi się i używam wielu odpowiedzi tutaj, więc wybiorę tę, która najbardziej Ci odpowiada. To powiedziawszy, metoda, którą wolę, gdy potrzebuję czegoś takiego jak cel-c, @synchronized
używa defer
instrukcji wprowadzonej w swift 2.
{
objc_sync_enter(lock)
defer { objc_sync_exit(lock) }
//
// code of critical section goes here
//
} // <-- lock released when this block is exited
Zaletą tej metody jest to, że krytyczna sekcja może opuścić blok zawierający w jakikolwiek sposób pożądany (np return
, break
, continue
, throw
) oraz „sprawozdanie w zestawieniu odroczyć są realizowane bez względu na to w jaki sposób program kontroli jest przekazywana”. 1
lock
? Jak jest lock
inicjowany?
lock
jest dowolnym obiektem celu-c.
Można wstawiać instrukcje między objc_sync_enter(obj: AnyObject?)
i objc_sync_exit(obj: AnyObject?)
. Słowo kluczowe @synchronized używa tych metod pod przykryciem. to znaczy
objc_sync_enter(self)
... synchronized code ...
objc_sync_exit(self)
objc_sync_enter
i objc_sync_exit
są metodami zdefiniowanymi w Objc-sync.h i są open source: opensource.apple.com/source/objc4/objc4-371.2/runtime/…
objc_sync_enter(…)
i objc_sync_exit(…)
są nagłówkami publicznymi udostępnianymi przez iOS / macOS / etc. Interfejsy API (wygląda na to, że znajdują się ….sdk
na ścieżce usr/include/objc/objc-sync.h
) . Najłatwiejszym sposobem sprawdzenia, czy coś jest publicznym interfejsem API, jest (w Xcode) wpisanie nazwy funkcji (np. objc_sync_enter()
Argumenty nie muszą być określone dla funkcji C) , a następnie spróbuj kliknąć ją. Jeśli pokazuje plik nagłówka dla tego interfejsu API, oznacza to, że jesteś dobry (ponieważ nie byłby on w stanie zobaczyć nagłówka, gdyby nie był publiczny) .
Analogicznie do @synchronized
dyrektywy z Objective-C może mieć dowolny typ zwrotu i ładne rethrows
zachowanie w 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()
}
Zastosowanie defer
instrukcji pozwala bezpośrednio zwrócić wartość bez wprowadzania zmiennej tymczasowej.
W Swift 2 dodaj @noescape
atrybut do zamknięcia, aby umożliwić więcej optymalizacji:
// 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()
}
Na podstawie odpowiedzi z GNewc [1] (gdzie lubię dowolny typ zwrotu) i Tod Cunningham [2] (gdzie lubię defer
).
SWIFT 4
W Swift 4 możesz używać kolejek dyspozytorskich GCD do blokowania zasobów.
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
wydaje się być niedostępny. Ale .concurrent
jest dostępny. : /
myObject.state = myObject.state + 1
działałbyś jednocześnie, nie policzyłby wszystkich operacji, ale zamiast tego dałby wartość niedeterministyczną. Aby rozwiązać ten problem, kod wywołujący powinien być zawinięty w kolejkę szeregową, aby zarówno odczyt, jak i zapis odbywały się atomowo. Oczywiście Obj-c @synchronised
ma ten sam problem, więc pod tym względem Twoja implementacja jest poprawna.
myObject.state += 1
jest kombinacją operacji odczytu, a następnie operacji zapisu. Niektóre inne wątki wciąż mogą pojawiać się między nimi, aby ustawić / zapisać wartość. Zgodnie z objc.io/blog/2018/12/18/atomic-variables łatwiej byłoby uruchomić set
blok synchronizacji / zamknięcia, a nie pod samą zmienną.
Aby dodać funkcję zwrotu, możesz to zrobić:
func synchronize<T>(lockObj: AnyObject!, closure: ()->T) -> T
{
objc_sync_enter(lockObj)
var retVal: T = closure()
objc_sync_exit(lockObj)
return retVal
}
Następnie możesz zadzwonić za pomocą:
func importantMethod(...) -> Bool {
return synchronize(self) {
if(feelLikeReturningTrue) { return true }
// do other things
if(feelLikeReturningTrueNow) { return true }
// more things
return whatIFeelLike ? true : false
}
}
Korzystając z odpowiedzi Bryana McLemore'a, rozszerzyłem ją, aby obsługiwała obiekty rzucające się do bezpiecznej posiadłości dzięki zdolności odroczenia Swift 2.0.
func synchronized( lock:AnyObject, block:() throws -> Void ) rethrows
{
objc_sync_enter(lock)
defer {
objc_sync_exit(lock)
}
try block()
}
rethrows
aby uprościć korzystanie z zamknięciami nie rzucającymi (nie trzeba używać try
), jak pokazano w mojej odpowiedzi .
Szybki 3
Ten kod ma możliwość ponownego wprowadzania i może współpracować z wywołaniami funkcji asynchronicznych. W tym kodzie, po wywołaniu someAsyncFunc (), inne zamknięcie funkcji w kolejce szeregowej będzie przetwarzane, ale będzie blokowane przez semaphore.wait (), dopóki nie zostanie wywołane signal (). InternalQueue.sync nie powinien być używany, ponieważ zablokuje główny wątek, jeśli się nie mylę.
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 nie jest dobrym pomysłem bez obsługi błędów.
W sesji „Zrozumienie awarii i dzienników awarii” 414 WWDC w 2018 r. Pokazują one następujący sposób przy użyciu DispatchQueues z synchronizacją.
W swift 4 powinno być coś takiego:
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
}
}
}
}
W każdym razie możesz także przyspieszyć odczytywanie, używając równoległych kolejek z barierami. Odczyty synchronizacji i asynchroniczne są wykonywane jednocześnie, a zapisanie nowej wartości czeka na zakończenie poprzednich operacji.
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
}
}
}
Użyj NSLock w 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()
Ostrzeżenie Klasa NSLock wykorzystuje wątki POSIX do implementacji swojego zachowania blokującego. Podczas wysyłania wiadomości odblokowującej do obiektu NSLock należy upewnić się, że wiadomość została wysłana z tego samego wątku, który wysłał początkową wiadomość blokującą. Odblokowanie blokady z innego wątku może spowodować niezdefiniowane zachowanie.
W nowoczesnym Swift 5 z funkcją powrotu:
/**
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()
}
Użyj go w ten sposób, aby skorzystać z możliwości zwracania wartości:
let returnedValue = synchronized(self) {
// Your code here
return yourCode()
}
Lub tak inaczej:
synchronized(self) {
// Your code here
yourCode()
}
GCD
). Wydaje się, że w zasadzie nikt nie używa ani nie rozumie, jak używać Thread
. Jestem z tego bardzo zadowolony - podczas gdy GCD
jest on pełen gotch i ograniczeń.
Wypróbuj: NSRecursiveLock
Blokada, którą można wielokrotnie nabyć przez ten sam wątek bez powodowania impasu.
let lock = NSRecursiveLock()
func f() {
lock.lock()
//Your Code
lock.unlock()
}
func f2() {
lock.lock()
defer {
lock.unlock()
}
//Your Code
}
Rysunek: Opublikuję moją implementację Swift 5, opartą na wcześniejszych odpowiedziach. Dzięki chłopaki! Uważam, że warto mieć taką, która zwraca również wartość, więc mam dwie metody.
Oto prosta klasa do zrobienia jako pierwsza:
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()
}
}
Następnie użyj go tak, jeśli potrzebujesz wartości zwracanej:
return Sync.syncedReturn(self, closure: {
// some code here
return "hello world"
})
Lub:
Sync.synced(self, closure: {
// do some work synchronously
})
public class func synced<T>(_ lock: Any, closure: () -> T)
, działa zarówno na void, jak i na inne typy. Są też rzeczy odrastające.
xCode 8.3.1, szybki 3.1
Odczytaj wartość zapisu z różnych wątków (asynchronicznie).
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
}
rozszerzenie 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()
}
}
}
klasa 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)")
}
}
}
W przypadku opakowań właściwości Swift używam teraz:
@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
}
}
Następnie możesz po prostu zrobić:
@NCCSerialized var foo: Int = 10
lub
@NCCSerialized var myData: [SomeStruct] = []
Następnie uzyskaj dostęp do zmiennej w normalny sposób.
DispatchQueue
ukrytego przed użytkownikiem. Znalazłem to odniesienie SO, aby się uspokoić: stackoverflow.com/a/35022486/1060314
Podsumowując, tutaj podaj bardziej powszechny sposób, który obejmuje wartość zwracaną lub void i rzucaj
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()
}
}
Dlaczego to utrudnia i kłopot z zamkami? Użyj Barier Wysyłkowych.
Bariera wysyłki tworzy punkt synchronizacji w równoległej kolejce.
Podczas działania żaden inny blok w kolejce nie może działać, nawet jeśli jest równoległy i dostępne są inne rdzenie.
Jeśli to brzmi jak wyłączna blokada (zapis), to właśnie tak. Bloki bez barier można traktować jako współdzielone (czytane) zamki.
Tak długo, jak cały dostęp do zasobu odbywa się przez kolejkę, bariery zapewniają bardzo tanią synchronizację.
W oparciu o „eurobur” przetestuj przypadek podklasy
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
Inną metodą jest utworzenie nadklasy, a następnie jej odziedziczenie. W ten sposób możesz używać GCD bardziej bezpośrednio
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
}
}