NSArray et NSThread
Flo
Membre
Bonjour,
J'ai entendu ici et là que la classe NSArray était thread safe...
Cela signifie-t-il que par exemple si j'ai un premier thread qui parcours un NSArray (via les méthodes du NSFastEnumeration protocol) et qu'un deuxième tente d'insérer un objet en même temps, ce dernier sera bloqué jusqu'à la fin du parcours opéré par le premier ?
Ou dois-je moi même gérer ce genre de synchronisme via NSLock ?
J'ai entendu ici et là que la classe NSArray était thread safe...
Cela signifie-t-il que par exemple si j'ai un premier thread qui parcours un NSArray (via les méthodes du NSFastEnumeration protocol) et qu'un deuxième tente d'insérer un objet en même temps, ce dernier sera bloqué jusqu'à la fin du parcours opéré par le premier ?
Ou dois-je moi même gérer ce genre de synchronisme via NSLock ?
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
There are several advantages to using fast enumeration:
The enumeration is considerably more efficient than, for example, using NSEnumerator directly.
The syntax is concise.
Enumeration is “safeâ€"the enumerator has a mutation guard so that if you attempt to modify the collection during enumeration, an exception is raised.
Since mutation of the object during iteration is forbidden, you can perform multiple enumerations concurrently.
Je lis donc que l'on peut lire la même collection en concurrence éventuelle dans plusieurs threads puisque changer la collection dans la boucle provoque une exception.
Néanmoins ça m'arrange pas des masses cette affaire. Ce que je voudrais moi c'est que si TH2 essaye d'ajouter dans le NSArray pendant que TH1 le parcours et ben TH2 attente la fin du parcours de TH1.
J'ai lu des choses sur NSLock, serait-il possible de l'utiliser pour que quand TH2 fasse un lock il soit mis en attente jusqu'à ce que TH1 ai fait un unlock ?
Si ça marche ce n'est possible que dans ce sens il me semble :
Plutôt que NSLock moi je verrais plutôt une directive [tt]@synchronized(...) { ... }[/tt] (qui a grosso modo le même but que NSLock, protéger un bout de code contre les accès concurrenciels par plusieurs threads, mais est je trouve pour ce genre de situation plus simple à utiliser.
En l'occurence, comme c'est l'objet passé à @synchronized(...) qui sert pour identifier/différencier le lock ou mutex sous-jacent créé, autrement dit @synchronized( a ) va créer un mutex associé à l'objet a, donc tous les codes entourés de @synchronized( a ) ne pourront être accédés que chacun son tour et pas concurrenciellement. Donc comme objet, tu peux par exemple mettre justement le NSArray auquel tu veux accéder.
Avec ce genre de truc ça devrait faire les choses propres je pense, en encadrant tous tes accès (en lecture et/ou écriture) à ton tableau tab par @synchronized(tab)... enfin si j'ai bonne mémoire, faudrait relire la doc pour confirmer, mais bon, l'idée est là .
Ouais mais l'exception, elle est envoyée dans le thread1 qui parcours le NSArray ou dans le thread2 qui essaye d'y accéder pendant qu'il est parcouru par thread1 ?
L'idéal ce serait dans thread2...
Pas bête , j'avais oublié @synchronised... mais ces mécanismes ne sont pas mis de base dans NSArray ?
[EDIT] Enfin sauf quand on est dans les méthodes du NSFastEnumeration protocol biensur...
Si je comprends bien et que j'entoure toutes les sections critiques de code de modifications/accès du tableau ça va bloquer les autres threads (genre le principal qui accède au tableau pour l'affichage des éléments du tableau (GUI))...
A chaque mise à jour du tableau, l'affichage sera bloqué, l'utilisateur pourra plus rien faire dessus jusqu'à la fin de la maj... pour peu que ça connexion internet soit pas tip top il peut aller se prendre un café :-\\
Comme en plus c'est des mises à jour environ toutes les deux minutes...
Yaurait pas moyen d'optimiser tout ça ?
Une solution en dédoublant les tableaux, et en faisant la mise à jour une fois les threads terminés, cela ne te va pas ?
Ben c'est que l'est pas tout p'tit le bazar ! J'me vois mal le copier et le bazarder ensuite...
Sinon je peux décider de ne pas mettre les fonctions de lectures du tableau en section critique pour ne pas bloquer les méthode du NSOutlineView data source protocol et donc l'affichage des éléments du tableau...
Qu'en pensez-vous ?
ça doit être un réflexe de faire tout ce qui peut causer une modification dans la GUI au niveau du thread principal.
Tu mets "waitUntilDone" à false, comme ça tu n'as même pas à attendre (je ne sais pas comment ça fonctionne... ça passe sans doute par les events).
Vous en pensez quoi ?
Sinon avec cette solution, dois-je toujours utiliser @synchronize sur chacun des petit tableaux ?
Le seul cas que je vois de problématique c'est quand le thread de "background update" de demande une copie d'un petit tableau et qu'en même temps le thread principal y accède/modifie... au pire si la copie se fait avant, l'éléments ne sera mis à jour que la prochaine fois qu'on recopie ce même petit tableau...
[EDIT] Désolé pour le retard, j'étais en Normandie pour le wee de pâques... (voilà , vous savez tout...;-))
Tu crois pas t'en sortir à si bon compte quand même :P
Le truc que je t'ai donné avec le "performSelectorOnMainThread" c'est un grand classique... c'est ce qui est fait partout (pour les graphiques en particulier...).
Ok, donc si je comprends bien il faudrait que je lance la mise à jour dans le thread principal même si on doit attendre que cette dernière soit terminée pour l'affichage et les modifs ? Si la maj prends un peu de temps ça risque pas de figer l'appli et de faire penser à un bug ?
S'il y a des calculs à faire, il faut qu'ils soient fait avant dans le thread de calcul avec éventuellement gestion d'un tableau à merger ensuite au principal.
Si vraiment il y a beaucoup de changements, que ça prend beaucoup de temps etc., travailler sur une copie, puis demander au thread principal de faire le switch entre les deux.
Honnêtement, ça me gène beaucoup de monopoliser le thread principal pour faire ça, il me semble plus judicieux de faire travailler un thread concurrent sur une copie... comme ça le thread principal peut toujours ajouter des éléments/afficher les éléments du tableau pendant que le thread concurrent les met à jour toujours en travaillant sur une nouvelle copie à chaque fois qu'il se déclenche (environs toutes les 2 minutes).
Le seul truc à faire c'est de protéger le tableau au moment où le thread concurrent en fait une copie avec @synchronized...
en gros là où moi je vois une tache monolithique(télécharger + mettre à jour dans un thread concurrent) toi tu proposes plutôt de fragmenter le processus pour ne laisser que la mise à jour des éléments au thread principal.
En fait ma mise à jour ce passe en trois étapes :
- construction de l'URL à partir des éléments du tableau (thread principal ?)
- téléchargement des données sur internet (thread concurrent)
- mise à jour des éléments concernés (thread principal)
Je commence à y voir un peu plus clair...
Ce qu'il me semble bon de faire :
- 1: dans le thread principal construire l'URL à partir des éléments du tableau puis lancer un thread concurrent.
- 2: dans un thread concurrent lancer le téléchargement des données depuis internet
- 3: une fois le thread concurrent terminée mettre à jour les éléments du tableau concernés.
Mais comment faire en sorte de démarrer l'étape 3 quand l'étape 2 est terminée ? Une notification ?
Comment transférer les données récupérées depuis le thread concurrent au thread principal ? var globale ?
Bon daccord ça fait plus d'une question mais que voulez vous... j'en profite !
J'ai pas dû le dire assez de fois... :P "performSelectorOnMainThread"...
Un objet auto-released en mettant YES pour "waitUntilDone"
J'ai pensé à ça :
PHASE 1
PHASE 2
PHASE 3
C'est à peu près ça que tu évoquais ou alors pas du tout ?
Le seul problème que je vois c'est que l'utilisateur peut ajouter des nouveaux éléments dans le tableau pendant la phase 2 et donc les updates fournis à la phase 3 ne prennent pas en compte ces nouveaux éléments. La méthode update() va parcourir le tableau qui contient potentiellement des éléments non concernés par la chaà®ne (NSString *)updates.
Il faut que je prévois un test de contrôle pour savoir si oui ou non un élément peut être mis à jour par la chaà®ne updates (ou si ce même élément à été pris en compte lors de la précédente construction d'url).
ça va alourdir un peu, mais c'est peut-être rien à côté du gain réalisé grace à l'économie des outils de synchronisation.
Merci en tous cas !
Ce que je fais dans ce cas (utilisé pour SudokuX), c'est que chaque modification utilisateur relance le thread... Une i-var pointeur conserve le pointeur du dernier thread lancé (affectation atomique, donc pas besoin de mutex), et chaque thread teste à des endroits stratégiques s'il est le dernier thread et stoppe s'il ne l'est pas...
ça permet aussi de mettre une temporisation au début de la méthode du thread avant le test pour bufferiser les modifications utilisateur s'il y en a plusieurs rapprochées.
"[NSThread currentThread]" permet de récupérer le pointeur du thread courant, pour à la fois l'update du pointeur " dernier thread " et pour les tests.
J'ai mis longtemps à concevoir cet algo de fonctionnement, mais j'en suis plutôt satisfait :P
Tu peux ! c'est très intéressant !
Je suppose, à la lecture de ce que tu as écris, que tu fais les ajouts utilisateur dans un nouveau thread ?
Par contre, si les ajouts utilisateurs nécessitent des calculs longs, il vaut mieux passer par un thread pour ne pas coincer l'interface oui (tout en faisant le merge final dans le thread principal).
j'ai déclaré une property pour mon controller :
PHASE 1
PHASE 2
PHASE 3
AJOUT UTILISATEUR (Dans le thread principal)
En gros s'il y a eu un ajout pendant le téléchargement, la variable lastThread pointe sur le Thread principal et la méthode update ne mettra pas à jour le tableau mais relancera la méthode backUpdate.
C'est ça l'idée ou pas du tout ?
Mais ça n'est valable que si une modification utilisateur invalide la mise à jour en cours... Sinon il faut un mécanisme de cumulation.
En réalité si un élément est inséré pendant la mise à jour en cours, il faut stopper cette dernière et la relancer pour qu'elle le prenne en compte.
Mais l'ajout d'un élément ne doit pas déclencher de mise à jour :
Que penses-tu de ça ? :
Auquel cas, c'est à peu de choses près le code que j'avais mis :
Le code que tu as mis au message précédent ne relance jamais le thread en cas de modification utilisateur.
Le thread peut le mettre à "nil" alors qu'il a été setté par un autre thread déjà , ce qui est problématique (ça flanque à l'eau la mise à jour du thread qui aurait alors été lancé)...
Pour pallier à ça, faire la mise à jour aussi si self.lastThread==nil pourrait être la solution. À y réfléchir si ça n'introduit pas d'effet de bord désagréable...