• Aucun résultat trouvé

Données propres à un thread

Dans le document Programmation Avancée sous Linux (Page 78-82)

Contrairement aux processus, tous les threads d’un même programme partagent le même espace d’adressage. Cela signifie que si un thread modifie une valeur en mémoire (par exemple, une variable globale), cette modification est visible dans tous les autres threads. Cela permet à plusieurs threads de manipuler les mêmes données sans utiliser les mécanismes de communication interprocessus (décrits au Chapitre 5).

Néanmoins, chaque thread a sa propre pile d’appel. Cela permet à chacun d’exécuter un code différent et d’utiliser des sous-routines la façon classique. Comme dans un programme monothreadé, chaque invocation de sous-routine dans chaque thread a son propre jeu de variables locales, qui sont stockées dans la pile de ce thread.

Cependant, il peut parfois être souhaitable de dupliquer certaines variables afin que chaque thread dispose de sa propre copie. GNU/Linux supporte cette fonctionnalité avec une zone de données propres au thread. Les variables stockées dans cette zone sont dupliquées pour chaque thread et chacun peut modifier sa copie sans affecter les autres. Comme tous les threads partagent le même espace mémoire, il n’est pas possible d’accéder aux données propres à un thread via des références classiques. GNU/Linux fournit des fonction dédiées pour placer des valeurs dans la zone de données propres au thread et les récupérer.

Vous pouvez créer autant d’objets de données propres au thread que vous le désirez, chacune étant de type void*. Chaque objet est référencé par une clé. Pour créer une nouvelle clé, et donc un nouvel objet de données pour chaque thread, utilisez pthread_key_create. Le premier argument est un pointeur vers une variablepthread_key_t. Cette clé peut être utilisée par chaque thread pour accéder à sa propre copie de l’objet de données correspondant. Le second argument de pthread_key_createest une fonction de libération des ressources. Si vous passez un pointeur sur une fonction, GNU/Linux appelle celle-ci automatiquement lorsque chaque thread se termine en lui passant la valeur de la donnée propre au thread correspondant à la clé. Ce mécanisme

4.3. DONNÉES PROPRES À UN THREAD 65 est très pratique car la fonction de libération des ressources est appelée même si le thread est annulé à un moment quelconque de son exécution. Si la valeur propre au thread est nulle, la fonction de libération de ressources n’est pas appelée. Si vous n’avez pas besoin de fonction de libération de ressources, vous pouvez passer NULL au lieu d’un pointeur de fonction.

Une fois que vous avez créé une clé, chaque thread peut positionner la valeur correspondant à cette clé en appelant pthread_setspecific. Le premier argument est la clé, et le second est la valeur propre au thread à stocker sous forme devoid*. Pour obtenir un objet de données propre au thread, appelez pthread_getspecific, en lui passant la clé comme argument.

Supposons, par exemple, que votre application partage une tâche entre plusieurs threads.

À des fins d’audit, chaque thread doit avoir un fichier journal séparé dans lequel des messages d’avancement pour ce thread sont enregistrés. La zone de données propres au thread est un endroit pratique pour stocker le pointeur sur le fichier journal dans chaque thread.

Le Listing 4.7 présente une implémentation d’un tel mécanisme. La fonction main de cet exemple crée une clé pour stocker le pointeur vers le fichier propre à chaque thread puis la stocke au sein de la variable globale thread_lock_key. Comme il s’agit d’une variable globale, elle est partagée par tous les threads. Lorsqu’un thread commence à exécuter sa fonction, il ouvre un fichier journal et stocke le pointeur de fichier sous cette clé. Plus tard, n’importe lequel de ces thread peut appeler write_to_thread_log pour écrire un message dans le fichier journal propre au thread. Cette fonction obtient le pointeur de fichier du fichier log du thread depuis les données propres au thread et y écrit le message.

Listing 4.7 – (tsd.c) – Fichiers Logs par Thread avec Données Propres au Thread

1 # i n c l u d e < m a l l o c . h >

32 {

33 i n t i ;

34 p t h r e a d _ t t h r e a d s [ 5 ] ;

35 /* C r é e une clé p o u r a s s o c i e r les p o i n t e u r s de f i c h i e r j o u r n a l d a n s les

36 d o n n é e s p r o p r e s au t h r e a d . U t i l i s e c l o s e _ t h r e a d _ l o g p o u r l i b é r e r

37 les p o i n t e u r s de f i c h i e r s . */

38 p t h r e a d _ k e y _ c r e a t e (& t h r e a d _ l o g _ k e y , c l o s e _ t h r e a d _ l o g ) ;

39 /* C r é e des t h r e a d s p o u r e f f e c t u e r les t r a i t e m e n t s . */

40 f o r ( i = 0; i < 5; ++ i )

41 p t h r e a d _ c r e a t e (&( t h r e a d s [ i ]) , NULL , t h r e a d _ f u n c t i o n , N U L L ) ;

42 /* A t t e n d la fin de t o u s les t h r e a d s . */

43 f o r ( i = 0; i < 5; ++ i )

44 p t h r e a d _ j o i n ( t h r e a d s [ i ] , N U L L ) ;

45 r e t u r n 0;

46 }

Remarquez quethread_functionn’a pas besoin de fermer le fichier journal. En effet, lorsque la clé du fichier a été créée, close_thread_log a été spécifiée comme fonction de libération de ressources pour cette clé. Lorsqu’un thread se termine, GNU/Linux appelle cette fonction, en lui passant la valeur propre au thread correspondant à la clé du fichier journal. Cette fonction prend soin de fermer le fichier journal.

Gestionnaires de Libération de Ressources

Les fonctions de libération de ressources associées aux clés des données spécifiques au thread peuvent être très pratiques pour s’assurer que les ressources ne sont pas perdues lorsqu’un thread se termine ou est annulé. Quelquefois, cependant, il est utile de pouvoir spécifier des fonctions de libération de ressources sans avoir à créer un nouvel objet de données propre au thread qui sera dupliqué pour chaque thread. Pour ce faire, GNU/Linux propose desgestionnaires de libération de ressources.

Un gestionnaire de libération de ressources est tout simplement une fonction qui doit être appelée lorsqu’un thread se termine. Le gestionnaire prend un seul paramètre void*et la valeur de l’argument est spécifiée lorsque le gestionnaire est enregistré – cela facilite l’utilisation du même gestionnaire pour libérer plusieurs instances de ressources.

Un gestionnaire de libération de ressources est une mesure temporaire, utilisé pour libérer une ressource uniquement si le thread se termine ou est annulé au lieu de terminer l’exécution d’une certaine portion de code. Dans des circonstances normales, lorsqu’un thread ne se termine pas et n’est pas annulé, la ressource doit être libérée explicitement et le gestionnaire de ressources supprimé.

Pour enregistrer un gestionnaire de libération de ressources, appelezpthread_cleanup_push, en lui passant un pointeur vers la fonction de libération de ressources et la valeur de son argument void*. L’enregistrement du gestionnaire doit être équilibré par un appel àpthread_cleanup_pop qui le supprime. Dans une optique de simplification, pthread_cleanup_pop prend un indicateur int en argument ; s’il est différent de zéro, l’action de libération de ressources est exécuté lors de la suppression.

L’extrait de programme du Listing 4.8 montre comment vous devriez utiliser un gestionnaire de libération de ressources pour vous assurer qu’un tampon alloué dynamiquement est libéré si le thread se termine.

4.3. DONNÉES PROPRES À UN THREAD 67 Listing 4.8 – (cleanup.c) – Extrait de Programme Montrant l’Utilisation d’un Gestionnaire de Libération de Ressources

Comme l’argument depthread_cleanup_pop est différent de zéro dans ce cas, la fonction de libération de ressourcesdeallocate_bufferest appelée automatiquement et il n’est pas nécessaire de le faire explicitement. Dans ce cas simple, nous aurions pu utiliser la fonction de la bibliothèque standard freecomme fonction de libération de ressources au lieu de deallocate_buffer. 4.3.1 Libération de ressources de thread en C++

Les programmeurs C++ sont habitués à avoir des fonctions de libération de ressources

«gratuitement» en plaçant les actions adéquates au sein de destructeurs. Lorsque les objets sont hors de portée, soit à cause de la fin d’un bloc, soit parce qu’une exception est lancée, le C++

s’assure que les destructeurs soient appelés pour les variables automatiques qui en ont un. Cela offre un mécanisme pratique pour s’assurer que le code de libération de ressources est appelé quelle que soit la façon dont le bloc se termine.

Si un thread appellepthread_exit, cependant, le C++ ne garantit pas que les destructeurs soient appelés pour chacune des variables automatiques situées sur la pile du thread. Une façon intelligente d’obtenir le même comportement est d’invoquerpthread_exit au niveau de la fonction de thread en lançant une exception spéciale.

Le programme du Listing 4.9 démontre cela. Avec cette technique, une fonction indique son envie de quitter le thread en lançant une ThreadExitException au lieu d’appeler pthread_exit directement. Comme l’exception est interceptée au niveau de la fonction de thread, toutes les variables locales situées sur la pile du thread seront détruites proprement lorsque l’exception est remontée.

Listing 4.9 – (cxx-exit.cpp) – Implémenter une Sortie de Thread en C++

Programmer en utilisant les threads demande beaucoup de rigueur car la plupart des pro-grammes utilisant les threads sont des propro-grammes concurrents. En particulier, il n’y a aucun moyen de connaître la façon dont seront ordonnancés les threads les uns par rapport aux autres.

Un thread peut s’exécuter pendant longtemps ou le système peut basculer d’un thread à un autre très rapidement. Sur un système avec plusieurs processeurs, le système peut même ordonnancer plusieurs threads afin qu’ils s’exécutent physiquement en même temps.

Déboguer un programme qui utilise les threads est compliqué car vous ne pouvez pas toujours reproduire facilement le comportement ayant causé le problème. Vous pouvez lancer le programme une première fois sans qu’aucun problème ne survienne et la fois suivante, il plante.

Il n’y a aucun moyen de faire en sorte que le système ordonnance les threads de la même façon d’une fois sur l’autre.

Dans le document Programmation Avancée sous Linux (Page 78-82)