A propos des threads

yodarkyodark Membre
05:21 modifié dans API AppKit #1
Bonjour à  tous

Petite question a propos des threads

Ma question est que se passe t-il si un thread est en train d'écrire une variable et en même temps l'application est en train de lire cette variable ?

Dans mon application je lance des threads ponctuellement afin de récolter les dernières informations à  jour provenant d'un  serveur web.

genre

mainapp Thread 0:
NSLog(i)

Thread 1
i = valeur_retourne_du_serveur

Depuis que j'utilise les thread mon application subit des plantages (aléatoirs ?) je pense que c'est a cause de ca :

<br />Thread 1<br />[lastGetTagsPosition release];<br />lastGetTagsPosition = [[[NSArray alloc] initWithArray:lastPosition] retain];


J'imagine que l'application était justement en train de lire lastGetTagsPosition au moment ou elle à  été releasé d'ou l'erreur. Est possible ? Peut-on réinitialiser un tableau sans le releasé ? est-ce risqué dans un thread ?


Réponses

  • AliGatorAliGator Membre, Modérateur
    05:21 modifié #2
    Héhé, tu viens de découvrir par toi-même le principal truc auquel il faut faire attention lorsqu'on travaille avec des threads : protéger l'accès aux variables qui sont partagées par plusieurs threads à  la fois.

    Pour protéger les variables entre les threads, cela s'appelle la synchronisation entre threads. Pour cela, tu as plusieurs solutions. En général, on passe par ce que l'on appelle des "locks" ou des "mutex". Mais l'Objective-C propose aussi la directive @synchronized qui est la plus simple de toutes à  utiliser.

    L'idée c'est d'encadrer l'accès à  ta variable, dans ton accesseur de ladite variable typiquement, avec des @synchronized(self), ce qui permet de s'assurer qu'un seul thread à  la fois n'accède à  ta variable.
    En fait, tout ce qui est encadré entre "@synchronized { ... }" ne peux être accédé que par seul thread à  la fois, si un autre veux y accéder il attendra que le premier sorte du "@synchronized"; avant d'y rentrer lui-même.

    Tout est expliqué ici dans la doc Apple, en particulier ici.

    De plus, si tu utilises les @property d'Objective-C 2.0, pense à  mettre toutes les propriétés qui pourraient être partagées entre plusieurs threads en mode "atomic" (et non pas "nonatomic") : cela aura pour effet de faire que leur accès sera comme si c'était une "action atomique", ne pouvant pas être interrompue par un autre thread... Donc en gros quand il génèrera l'accesseur si tu n'es pas "nonatomic", il va rajouter tout seul les "@synchronized"; qu'il faut. Ceci est expliqué ici aussi
  • yodarkyodark Membre
    05:21 modifié #3
    Très intéressant donc si je comprends bien ce que tu m'a expliqué et la doc :

    1. Soit je met toutes mes variable utilisée dans le thread en atomic

    2. Soit dans mon thread j'encadre par @synchronized tout le bloc qui écrit dans les variables. De ce fait, seule la connexion serveur sera threadisée mais une fois fait, tout est bloqué et le programme écrit dans mes variables la réponse serveur ?

    petite question encore, si je met en atomic lastGetTagsPosition rien n'empêche un autre thread de tenter de lire lastGetTagsPosition entre la ligne 1 et la ligne 2 au moment ou lastGetTagsPosition est releasé non ?

    Thread 1
    1. [lastGetTagsPosition release];<br />2. lastGetTagsPosition = [[[NSArray alloc] initWithArray:lastPosition] retain];
    


  • schlumschlum Membre
    05:21 modifié #4
    Faut entourer les 2 lignes de @synchronized...
    La prochaine fois, avec un peu de (mal)chance, tu découvriras le dead lock  :P
  • yodarkyodark Membre
    janvier 2009 modifié #5
    que veux tu dire schlum par tu connaitra le dead lock ?

    J'ai entouré mon thread par des

    @synchronize(self)
    {
    }
    Sur un peu près tout sauf pour la connexion au serveur

    NSURL * url=[NSURL URLWithString:getTagUrl];<br />NSError * error;<br />NSString * string=[NSString stringWithContentsOfURL:url encoding:NSISOLatin1StringEncoding error:&amp;error];
    


    pourtant j'ai encore des plantages comment ça se fait ? Ou bien faut-il mettre aussi les variable en atomic ? Quel est le désavantage de atomic ?
    Se pourrait il que le problème vienne d'ailleurs (sachant que ca se produit uniquement quand c'est thread ?)

    Edit :
    Dans mon thread principal j'ai pas mis de @synchronised en pensant que lorsque le parseur arrive sur un synchronised il bloque les autres thread en cours. Mais dois-je mettre un synchronised a chaque fois que la variable qui peut être écrite par un thread est lue ?
  • schlumschlum Membre
    05:21 modifié #6
    Le dead lock, c'est quand on a tout bien protégé, mais quand les threads sont mutuellement bloqués (ex : le thread A est en train d'attendre que le thread B termine quelque chose, et le thread B est en train d'attendre que le thread A termine quelque chose).
    On peut aussi en avoir dans un seul thread si on s'emmêle les pinceaux (si on essaie de bloquer un mutex qu'on a déjà  bloqué au dessus).
  • Philippe49Philippe49 Membre
    janvier 2009 modifié #7
    Une autre piste

    dans 1231789543:

    <br />Thread 1<br />[lastGetTagsPosition release];<br />lastGetTagsPosition = [[[NSArray alloc] initWithArray:lastPosition] retain];
    



    Sans rentrer dans la discussion sur les threads (tu es en de bonnes mains ...) le code que tu mets ne saurait être responsable d'un plantage puisque ton NSArray est retenue une première fois par alloc..init, puis par un retain. Donc le release ne détruit pas la NSArray. Cela serait plutôt une fuite mémoire qui pourrait s'en suivre. Celle-ci pourrait expliquer un plantage si cette NSArray manipule une très importante quantité de données, soit directement, soit par appels successifs.


    Par contre,
    dans 1231840569:

    NSURL * url=[NSURL URLWithString:getTagUrl];<br />NSError * error;<br />NSString * string=[NSString stringWithContentsOfURL:url encoding:NSISOLatin1StringEncoding error:&amp;error];
    


    ne retient pas string, ni url, et si tu espères la rappeller ailleurs, un plantage peut se produire.
  • AliGatorAliGator Membre, Modérateur
    05:21 modifié #8
    dans 1231840569:

    Dans mon thread principal j'ai pas mis de @synchronised en pensant que lorsque le parseur arrive sur un synchronised il bloque les autres thread en cours. Mais dois-je mettre un synchronised a chaque fois que la variable qui peut être écrite par un thread est lue ?
    En fait il faut entourer de [tt]@synchronized(self)[/tt] quand tu accèdes à  une variable, quel que soit le thread depuis lequel tu accèdes à  la variable. C'est pour ça qu'en fait on n'en met pas réellement "partout dans le code" mais qu'on utilise plutôt des accesseurs. Ainsi, tu n'accèdes jamais à  ta variable toto "directement" depuis l'extérieur de ta classe, mais tu utilises les méthodes "toto" (pour récupérer la valeur) et "setToto:" (pour modifier la valeur). Si tu respectes ça, il ne te reste plus qu'à  mettre des [tt]@synchronized(self)[/tt] à  l'intérieur de tes accesseurs "toto" et "setToto:", mais pas besoin d'en mettre "à  l'extérieur" de ta classe du coup.

    Par contre en fait il faut voir "@synchronized"; comme une sorte de barrière, ou de jeton. Quand ton code s'exécute, s'il rencontre un "@synchronized(A)";, il va prendre le jeton A associé à  cette "barrière" puis rentrer dans le bloc @synchronized. Si un autre thread arrive devant ce "@synchronized(A)";, il va aussi vouloir prendre le jeton A, mais il aura déjà  été pris, donc il va être bloqué et attendre devant cette "barrière". Mais dès que le premier thread a fini et sort du bloc "@synchronized";, il remet le jeton A en place, et du coup le 2e thread peut le prendre à  son tour.
    C'est un peu comme les toilettes publiques dans les grandes villes comme à  Paris : si y'a déjà  quelqu'un dans le bloc @synchronized quand toi tu veux rentrer, ben tu attends patiemment que l'autre sorte du bloc avant d'y rentrer à  ton tour :D

    La subtilité c'est qu'il peut y avoir plusieurs barrières utilisant le même jeton à  des endroits différents de ton code, pour éviter qu'un thread aille lire une variable pendant que l'autre la modifie, ou que 2 threads modifient ta variable en même temps même si la modification a leur dans 2 endroits différents du code... En effet, si tu protèges l'écriture de ta variable à  un endroit X mais pas à  un autre endroit Y de ton code, rien n'empêche un thread de passer par le bout de code X, ce qui nécessite qu'il passe la "barrière" du "@synchronized(self)"; mais pas de souci, c'est le premier à  passer par là ... et que pendant ce temps là , un 2e thread passe par le code Y, qui lui n'est pas protégé, donc rien ne l'arrête et ne l'empêche d'aller sur ce code et modifier du coup lui aussi la variable en même temps que le premier thread !

    C'est pour ça qu'il faut mettre des barrières/verrous (comme @synchronized(self) ou NSLock) partout où ta variable à  protéger est lue ou modifiée, pas que dans les threads autre que le thread principal (en fait tu t'en fiches de savoir quel thread ira utiliser tel bout de code ou pas, que ce soit le thread principal ou un autre c'est exactement la même problématique). Et c'est pour ça aussi que du coup on utilise les accesseurs (getter/setter) pour lire et modifier la variable, comme ça on n'a à  mettre les @synchronized QUE dans ces accesseurs, et on passe ensuite par ces accesseurs pour lire ou modifier la variable et non pas par la variable elle-même (ou alors si à  l'intérieur de ta classe tu modifiers ladite variable directement, faut aussi l'entourer de @synchronized). De l'extérieur de la classe, comme on appelle les accesseurs, qui contiennent les @synchronized, pas de soucis c'est protégé.


    dans 1231840569:

    pourtant j'ai encore des plantages comment ça se fait ? Ou bien faut-il mettre aussi les variable en atomic ? Quel est le désavantage de atomic ?
    Sans doute donc du coup parce que tu n'avais pas mis de @synchronized partout, ou n'utilisais pas les accesseurs.

    Les "atomic" (ou plutôt ne pas préciser "nonatomic", puisque c'est "atomic" par défaut pour les properties) c'est pour les variables qui sont déclarées en @property et donc pour lesquelles dans le .m tu as un @synthetize qui va générer automatiquement les accesseurs (setter/getter). Si tu ne mets pas "nonatomic", et donc que ces @property sont déclarées comme atomiques, lors de la création des setters/getters il va tout seul les protéger avec des @synchronized à  l'intérieur du code qu'il va générer. Donc pour toutes les @property que tu risques d'accéder depuis plusieurs threads à  la fois, il faut aussi les mettre en "atomic" (ne pas préciser "nonatomic" quoi).
    Par contre si ce sont des propriétés qui n'ont pas besoin d'être accédées/modifiées par plusieurs threads (qui n'ont pas besoin d'être "thread-safe" comme on dit), c'est mieux de les mettre "nonatomic" et donc d'éviter qu'il rajoute les @synchronized et autres barrières éventuelles, car ça évite de ralentir le code pour rien.
Connectez-vous ou Inscrivez-vous pour répondre.