J'ai cherché dans le livre Swift, mais je ne trouve pas la version Swift de @synchronized. Comment puis-je faire de l'exclusion mutuelle dans Swift?
removeFirst()
?
J'ai cherché dans le livre Swift, mais je ne trouve pas la version Swift de @synchronized. Comment puis-je faire de l'exclusion mutuelle dans Swift?
removeFirst()
?
Réponses:
Vous pouvez utiliser GCD. Il est un peu plus verbeux que @synchronized
, mais fonctionne comme un remplacement:
let serialQueue = DispatchQueue(label: "com.test.mySerialQueue")
serialQueue.sync {
// code
}
Je le cherchais moi-même et je suis arrivé à la conclusion qu'il n'y avait pas encore de construction native dans swift pour cela.
J'ai créé cette petite fonction d'aide basée sur une partie du code que j'ai vu de Matt Bridges et d'autres.
func synced(_ lock: Any, closure: () -> ()) {
objc_sync_enter(lock)
closure()
objc_sync_exit(lock)
}
L'utilisation est assez simple
synced(self) {
println("This is a synchronized closure")
}
Il y a un problème que j'ai trouvé avec cela. Passer dans un tableau en tant qu'argument de verrouillage semble provoquer une erreur de compilation très obtuse à ce stade. Sinon, cela semble fonctionner comme souhaité.
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
bloc, mais notez qu'il n'est pas identique à une véritable instruction de bloc intégrée comme le @synchronized
bloc dans Objective-C, car les instructions return
et break
ne fonctionnent plus pour sortir de la fonction / boucle environnante comme ce serait le cas s'il s'agissait d'une déclaration ordinaire.
defer
mot clé pour vous assurer d' objc_sync_exit
être appelé même en cas de closure
lancement.
J'aime et utilise de nombreuses réponses ici, donc je choisirais celle qui vous convient le mieux. Cela dit, la méthode que je préfère lorsque j'ai besoin de quelque chose comme objective-c @synchronized
utilise la defer
déclaration introduite dans 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 chose gentille au sujet de cette méthode, est que votre section critique peut sortir du bloc contenant une manière désirée quelconque (par exemple, return
, break
, continue
, throw
), et « les déclarations dans l'instruction defer sont exécutées , peu importe la façon dont le contrôle du programme est transféré. » 1
lock
? Comment est lock
initialisé?
lock
est tout objet objectif-c.
Vous pouvez prendre en sandwich les déclarations entre objc_sync_enter(obj: AnyObject?)
et objc_sync_exit(obj: AnyObject?)
. Le mot clé @synchronized utilise ces méthodes sous les couvertures. c'est à dire
objc_sync_enter(self)
... synchronized code ...
objc_sync_exit(self)
objc_sync_enter
et objc_sync_exit
les méthodes sont définies dans Objc-sync.h et sont open source: opensource.apple.com/source/objc4/objc4-371.2/runtime/…
objc_sync_enter(…)
et objc_sync_exit(…)
sont des en-têtes publics fournis par iOS / macOS / etc. API (on dirait qu'elles sont à l'intérieur ….sdk
du chemin d'accès usr/include/objc/objc-sync.h
) . La façon la plus simple de savoir si quelque chose est une API publique ou non est de (dans Xcode) taper le nom de la fonction (par exemple objc_sync_enter()
; les arguments n'ont pas besoin d'être spécifiés pour les fonctions C) , puis essayez de cliquer avec la commande. S'il vous montre le fichier d'en-tête pour cette API, alors vous êtes bon (puisque vous ne pourriez pas voir l'en-tête s'il n'était pas public) .
L'analogue de la @synchronized
directive d'Objective-C peut avoir un type de retour arbitraire et un rethrows
comportement agréable dans 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'utilisation de l' defer
instruction permet de renvoyer directement une valeur sans introduire de variable temporaire.
Dans Swift 2, ajoutez l' @noescape
attribut à la fermeture pour permettre plus d'optimisations:
// 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()
}
Basé sur les réponses de GNewc [1] (où j'aime le type de retour arbitraire) et Tod Cunningham [2] (où j'aime defer
).
SWIFT 4
Dans Swift 4, vous pouvez utiliser les files d'attente de répartition GCD pour verrouiller les ressources.
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
semble indisponible. Mais .concurrent
est disponible. : /
myObject.state = myObject.state + 1
simultanément, il ne comptera pas le nombre total d'opérations mais produira à la place une valeur non déterministe. Pour résoudre ce problème, le code appelant doit être encapsulé dans une file d'attente série afin que la lecture et l'écriture se produisent de manière atomique. Bien sûr, Obj-c @synchronised
a le même problème, donc dans ce sens, votre implémentation est correcte.
myObject.state += 1
est une combinaison d'une opération de lecture puis d'écriture. Un autre thread peut encore entrer entre définir et écrire une valeur. Selon objc.io/blog/2018/12/18/atomic-variables , il serait plus facile d'exécuter le set
dans un bloc de synchronisation / fermeture à la place et non sous la variable elle-même.
En utilisant la réponse de Bryan McLemore, je l'ai étendu pour prendre en charge les objets qui jettent dans un manoir sûr avec la capacité de report de Swift 2.0.
func synchronized( lock:AnyObject, block:() throws -> Void ) rethrows
{
objc_sync_enter(lock)
defer {
objc_sync_exit(lock)
}
try block()
}
rethrows
pour simplifier l'utilisation avec des fermetures sans lancement (pas besoin d'utiliser try
), comme indiqué dans ma réponse .
Pour ajouter une fonction de retour, vous pouvez procéder comme suit:
func synchronize<T>(lockObj: AnyObject!, closure: ()->T) -> T
{
objc_sync_enter(lockObj)
var retVal: T = closure()
objc_sync_exit(lockObj)
return retVal
}
Par la suite, vous pouvez l'appeler en utilisant:
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
Ce code a la possibilité de rentrer et peut fonctionner avec les appels de fonction asynchrones. Dans ce code, après l'appel de someAsyncFunc (), une autre fermeture de fonction sur la file d'attente série sera traitée mais bloquée par semaphore.wait () jusqu'à ce que signal () soit appelé. internalQueue.sync ne doit pas être utilisé car il bloquera le thread principal si je ne me trompe pas.
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 n'est pas une bonne idée sans gestion des erreurs.
Dans la session "Understanding Crashes and Crash Logs" 414 de la WWDC 2018, ils montrent la manière suivante d'utiliser DispatchQueues avec synchronisation.
Dans swift 4 devrait être quelque chose comme ceci:
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
}
}
}
}
Quoi qu'il en soit, vous pouvez également effectuer des lectures plus rapidement en utilisant des files d'attente simultanées avec des barrières. Les lectures synchronisées et asynchrones sont effectuées simultanément et l'écriture d'une nouvelle valeur attend la fin des opérations précédentes.
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
}
}
}
Utilisez NSLock dans 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()
Avertissement La classe NSLock utilise des threads POSIX pour implémenter son comportement de verrouillage. Lors de l'envoi d'un message de déverrouillage à un objet NSLock, vous devez être sûr que le message est envoyé à partir du même thread qui a envoyé le message de verrouillage initial. Déverrouiller un verrou d'un thread différent peut entraîner un comportement indéfini.
Dans le Swift 5 moderne, avec possibilité de retour:
/**
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()
}
Utilisez-le comme ceci, pour profiter de la capacité de valeur de retour:
let returnedValue = synchronized(self) {
// Your code here
return yourCode()
}
Ou comme ça autrement:
synchronized(self) {
// Your code here
yourCode()
}
GCD
). Il semble que personne n'utilise ou ne comprenne comment l'utiliser Thread
. J'en suis très satisfait - alors qu'il GCD
est semé d'embûches et de limitations.
Essayez: NSRecursiveLock
Un verrou qui peut être acquis plusieurs fois par le même thread sans provoquer de blocage.
let lock = NSRecursiveLock()
func f() {
lock.lock()
//Your Code
lock.unlock()
}
func f2() {
lock.lock()
defer {
lock.unlock()
}
//Your Code
}
Je vais publier mon implémentation de Swift 5, basée sur les réponses précédentes. Merci les gars! J'ai trouvé utile d'en avoir un qui renvoie également une valeur, j'ai donc deux méthodes.
Voici une classe simple à faire en premier:
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()
}
}
Ensuite, utilisez-le comme si vous aviez besoin d'une valeur de retour:
return Sync.syncedReturn(self, closure: {
// some code here
return "hello world"
})
Ou:
Sync.synced(self, closure: {
// do some work synchronously
})
public class func synced<T>(_ lock: Any, closure: () -> T)
, fonctionne pour les deux, nul et tout autre type. Il y a aussi les trucs de repousse.
xCode 8.3.1, swift 3.1
Lire la valeur d'écriture à partir de différents threads (async).
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
}
extension 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)")
}
}
}
Avec les wrappers de propriétés de Swift, voici ce que j'utilise maintenant:
@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
}
}
Ensuite, vous pouvez simplement faire:
@NCCSerialized var foo: Int = 10
ou
@NCCSerialized var myData: [SomeStruct] = []
Accédez ensuite à la variable comme vous le feriez normalement.
DispatchQueue
qui est caché à l'utilisateur. J'ai trouvé cette référence SO pour me mettre à l'aise: stackoverflow.com/a/35022486/1060314
En conclusion, voici un moyen plus courant qui inclut la valeur de retour ou void, et lancez
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()
}
}
Pourquoi le rendre difficile et compliqué avec des serrures? Utilisez des barrières d'expédition.
Une barrière de répartition crée un point de synchronisation dans une file d'attente simultanée.
Pendant son exécution, aucun autre bloc de la file d'attente n'est autorisé à s'exécuter, même s'il est simultané et que d'autres cœurs sont disponibles.
Si cela ressemble à un verrou exclusif (écriture), ça l'est. Les blocs sans barrière peuvent être considérés comme des verrous partagés (en lecture).
Tant que tous les accès à la ressource sont effectués via la file d'attente, les barrières offrent une synchronisation très bon marché.
Basé sur ɲeuroburɳ , testez un cas de sous-classe
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
Une autre méthode consiste à créer une superclasse puis à en hériter. De cette façon, vous pouvez utiliser GCD plus directement
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
}
}