A propos des threads
yodark
Membre
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 :
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 ?
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 ?
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
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
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
La prochaine fois, avec un peu de (mal)chance, tu découvriras le dead lock :P
J'ai entouré mon thread par des
@synchronize(self)
{
}
Sur un peu près tout sauf pour la connexion au serveur
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 ?
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).
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,
ne retient pas string, ni url, et si tu espères la rappeller ailleurs, un plantage peut se produire.
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
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é.
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.