@synchronized vs. dispatch_sync

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 :)


Réponses

  • samirsamir Membre

    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

  • AliGatorAliGator Membre, Modérateur
    mars 2016 modifié #3
    Attention, même s'ils peuvent dans certains contextes résoudre un problème similaire, @synchronized et dispatch_sync ne font conceptuallement pas du tout la même chose.
    • @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 ?


  • AliGatorAliGator Membre, Modérateur
    Oui, disons qu'on peut tout à  fait encore l'utiliser si ça t'amuse, mais ça fait une éternité que je ne l'ai pas vu dans une codebase moderne / récente.

    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.
  • samirsamir Membre
    mars 2016 modifié #6


    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.


     



    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 :


     


    As a precautionary measure, the @synchronized ;block implicitly adds an exception handler to the protected code. This handler automatically releases the mutex in the event that an exception is thrown. This means that in order to use the @synchronizeddirective, you must also enable Objective-C exception handling in your code. If you do not want the additional overhead caused by the implicit exception handler, you should consider using the lock classes

     



     


    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. 


  • MalaMala Membre, Modérateur
    mars 2016 modifié #8


    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 ?




    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.


  • MalaMala Membre, Modérateur


    À 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. 




    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 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.




     


    C'est ça que je n'ai pas compris, bloquer mon app ? tu parles de blockage du thread ou d'app ?

  • MalaMala Membre, Modérateur


    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. 

  • MalaMala Membre, Modérateur

    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...



    NSArray *myArray = [NSArray new];
     
    NSLog(@log 1);
     
    NSString *shitData = [myArray objectAtIndex:10];
     
    NSLog(@log 2);

    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...



    NSArray *myArray = [NSArray new];

    NSLog(@log 1);

    NSString *shitData = nil;
     
    @try
    {
        shitData = [myArray objectAtIndex:10];
    }
    catch(NSException *exception)
    {
        NSLog(@You're talking to my exception?!? You want to FUCK my exception!?!);
    }

    NSLog(@log 2);

    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


Connectez-vous ou Inscrivez-vous pour répondre.