@synchronized vs. dispatch_sync
NiClou
Membre
Bonjour à tous,
Je cherche à comprendre quel est la différence entre :
@synchronized (self.property) {
//Do some stuff
}
et
dispatch_sync(self.myQueue, ^{
//Do some stuff
});
J'ai cru comprendre que @synchronized était moins performant mais sans réelle explication.
Merci d'avance
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
Hello,
Voici un thread dont on explique bien ça : ( la réponse de bbum qui travaille chez d'Apple)
http://stackoverflow.com/questions/17599401/what-advantages-does-dispatch-sync-have-over-synchronized
- @synchronized positionne un lock (utilisant implicitement un lock associé à "self.property"). Il acquiert le lock au début du block et le relâche à la fin.
- dispatch_sync ou dispatch_async poussent un bloc de code sur une queue. Ce block est ensuite exécuté par la queue quand elle en a le temps, concept qui varie selon le type de queue (serial vs. concurrent) et sa QoS, entre autres (et le nombre de CPUs, threads, etc)
Après, si le problème que tu cherches à résoudre est de protéger un bout de code contre un accès concurrent en environnement multi-threadé, ce sont effectivement 2 solutions distinctes qui peuvent résoudre le problème. Mais elles ne le résolvent pas de la même manière.- La première le fait avec un verrou qu'elle acquiert (ce qui peut dans certains cas être coûteux, d'ailleurs, selon le type de lock)
- La seconde en empilant un bloc de code dans une FIFO série, qui garantit ne dépiler ces blocs de code que un par un si la queue est serial (si tu utilises ce code avec une queue de type concurrent, tout l'objectif de résoudre le problème d'accès concurrentiel tombe à l'eau !), et donc t'assure que 2 blocks dispatchés sur une même queue série ne pourront pas être exécutés en parallèle par 2 threads concurrents.
GCD et dispatch_sync peuvent être utilisés de bien d'autres façons, ou peuvent être utilisés de cette même façon mais pour résoudre des problèmes tout autres, donc ne pas confondre le problème à résoudre et les moyens de le résoudre, en tout cas ne pas faire le raccourci laissant penser que @synchronze vs dispatch_sync(serialQueue) sont interchangeables fonctionnellement. Ils ne font pas la même chose ce sont juste 2 façons différentes de résoudre un même problème.Bien avoir en tête que :
- dispatch_sync peut aussi être utilisé avec une queue concurrente. Et dans ce cas son intérêt et cas d'usage est tout autre.
- l'utilisation d'une queue serial peut aussi se faire avec dispatch_async, ce qui va permettre d'empiler les requête de code à exécuter, en ayant l'assurance qu'ils seront exécutés en série, mais sans pour autant bloquer l'appel et attendre qu'ils aient fini de s'exécuter pour continuer. Contrairement à l'utilisation d'un lock avec @synchronized ou toute autre solution qui ne permettrait pas ce genre de cas d'usage
- il est aussi possible d'utiliser un dispatch_sync ou dispatch_async avec une queue concurrente... et d'utiliser une "dispatch barrier" pour un bloc de code que tu dispatches pour le forcer à s'exécuter tout seul en série et non de manière concurrentielle avec les autres. Ceci est très pratique pour implémenter de la "lecture concurrentielle mais écriture bloquante" d'une ressource, et ainsi permettre d'optimiser les accès concurrents à cette ressource en limitant fortement les accès bloquants. Chose qui ne peut pas être implémentée simplement avec un lock classique ou un @synchronized.
- On peut utiliser une queue série pour d'autres choses (résoudre des problèmes différents) que la protection d'accès concurrent à une ressource ou d'une section critique, par exemple pour la stérilisation de tâches, où là l'objectif n'est pas vraiment de mettre un verrou pour empêcher l'accès à une ressource par un thread tant qu'un autre y accède déjà , mais plutôt s'assurer que des blocs de code, pourtant potentiellement empilés de manière asynchrones avec dispatch_async par exemple, vont s'exécuter dans un certain ordre, potentiellement parce qu'elles sont dépendantes entre elles par exemple.
Donc oui si le problème à résoudre est de protéger un accès concurrent, utiliser une queue série et un dispatch_sync peut être une alternative à @synchronized, et d'ailleurs de nos jours je conseillerai plutôt d'utiliser GCD car @synchronized est + coûteux et un peu has-been, mais il faut garder à l'esprit que ce ne sont pas juste 2 écritures différentes qui font la même chose, c'est plutôt 2 solutions différentes à un même problème dans un certain contexte, mais qui ne doit pas être généralisé sans comprendre ce qui se passe derrière, et ne pas imaginer que "dispatch_sync(myQueue)" ne sert qu'à ça, car selon le contexte (queue série vs. concurrente, QoS, etc) il a d'autres applications et d'autres cas d'usage qui n'ont potentiellement rien à voir.Ah bon, @synchronized est has-been ?
Et si tu regardes les divers articles de blogs qui font des comparatifs sur les différents mécanismes de lock qui peuvent être utilisés sur iOS ou OSX, du @synchronized au NSLock en passant par OSSpinLock ou même pthread_mutex_* et compagnie jusqu'aux APIs plus modernes type GCD, tu vois que l'implémentation de @synchronized est tout à fait honnête mais tout de même pas la plus rapide qui soit pour autant.
@synchronized doit son succès à l'époque surtout pour son côté pratique, de ne pas avoir à créer de NSLock ou d'objet spécialement pour stocker le lock, mais de permettre à la place de faire @synchronized(self) ou des équivalents qui créent le NSLock ou OSSpinLock (ou je ne sais plus quelle implémentation exacte est utilisée ça fait longtemps que j'ai pas regardé le code) pour toi sous le capot.
Donc c'est facile à utiliser sans avoir à créer au préalable un NSLock ou une dispatch_queue dédiée. Mais le côté facile vient aussi avec le côté pas le plus optimisé.
Alors après y'a pas non plus de mal à utiliser @synchronized, dans le sens où si tu le vois dans un vieux code c'est pas forcément une hérésie dont il faut absolument se débarrasser à tout prix, ça marche encore, c'est juste que pour du code créé aujourd'hui, ce n'est plus trop la mode surtout maintenant que GCD offre des outils bien plus puissants et une autre approche.
pthread_mutex_t ( NSRecursiveLock)
https://mikeash.com/pyblog/friday-qa-2015-02-20-lets-build-synchronized.html
http://www.opensource.apple.com/source/objc4/objc4-646/runtime/objc-sync.mm
Ce que je n'ai pas compris c'est la gestion des exception que la directive @synchronise rajoute au code. Voici le passage de la doc :
Une idée ?
À propos de @synchronized, Je l'ai utilisée récemment dans une classe qui possède un NSMutableDico et qui a des accès multithreads. Je n'avais pas pensé à utiliser GCD.
La doc me semble pourtant claire. Avec @synchronize tu as une gestion d'exception intégré au cas où quelque chose se passe mal entre tes accolades. C'est le genre de cas auquel il faut faire attention sinon tu as vite fait de bloquer ton appli en cas d'exception levée inopinément par ton code: si par exemple tu utilises un NSLock ou un pthread_mutex_t et que tu ne le délocke pas systématiquement.
Je ne sais pas si dans ton cas c'est envisageable mais d'expérience j'ai tendance à utiliser autant que faire se peut des NSCache. Ils sont thread safe d'origine et c'est beaucoup plus performant en terme d'accès par rapport à un dictio protégé à la mano.
C'est ça que je n'ai pas compris, bloquer mon app ? tu parles de blockage du thread ou d'app ?
Imagine que tu as une queue de traitement et que l'exécution d'un block laisse un NSLock locké à cause d'une levée d'exception comme on l'a évoqué. Les blocks suivant vont rester coincés en attente de la libération du lock qui protège la ressource convoitée. Que tu sois dans un thread séparé ou pas n'y change pas grand chose au final. Si ton appli a besoin du retour du traitements des données moulinées par les blocks et bien elle peut attendre longtemps et en toute logique ton utilisateur avec...
Est-ce plus clair dans ton esprit?
Merci @Mala.
Pour moi une levée d'exception qui n'est pas gérée est tout simplement un arrêt de l'application (crash). Donc tous les locks ou autres choses au niveau système vont entre "nettoyer" par le système.
Je pense que je ne sais pas c'est quoi réellement une exception!!! Je vais revoir ça.
Ha oui non en effet.
Tiens un exemple tout con à tester dans une méthode pour comprendre le truc: tu mets un NSArray que tu initialises sans rien dedans et tu essayes d'accéder à un objet à l'index 10 au hasard. Tu mets un NSLog avant et un NSLog après. Genre ça quoi...
Et là tu verras que ton deuxième log ne sera pas affiché à l'exécution. Par contre tu vas avoir un joli "index 10 beyond bounds for empty array" dans tes logs. Ton NSArray a levé une exception qui n'est pas gérée par notre code. Du coup, l'exécution de la méthode est court circuité au niveau de "objectAtIndex:". Pour autant, le programme ne va pas s'arrêter (l'exception est catchée par NSApplication ou UIApplication) c'est juste l'exécution de la méthode qui stop net.
Pour gérer des levées d'exception proprement dans ton code, il faut utiliser des try & catch. Ici pour l'exemple cela pourrait donner un truc du genre...
Perso, je peux pas saquer les exceptions en objective C. Elles font vraiment "pièces rapportées" dans le langage mais il faut faire avec.
Plus de doc ici...
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Exceptions/Exceptions.html