TD1 – Découverte des Threads

Ce TD sert d’introduction aux notions de threads et de parallélisme. Avant de commencer ce TD vous pouvez télécharger une template de programme. Cela vous fournira les éléments de compilations pour bien commencer. Si vous utilisez la machine virtuelle fourni pour ce TD, cette template se trouve dans le répertoire /opt/td-thread/

Création d’un premier thread

Pthread

Pthread est une librairie suivant la norme POSIX qui permet de gérer les threads. Cette API permet de s’abstraire des threads systèmes pour manipuler des threads peut importe le système. Elle contient aussi des fonctions permettant la synchronisation entre threads. Globalement cette librairie est portable sauf quelques fonctions taguées avec l’extension _np (non portable).

Comme les principales APIs POSIX vous pouvez retrouver leur documentation dans le man. Vous pouvez avoir un aperçu des fonctions Pthread en les listant:

man -k . | grep "pthread_"

Puis vous pouvez obtenir plus d’informations, exemples… sur une fonction en tapant:

man [fonction]

Dans un premier temps, nous n’utiliserons qu’une petite partie de ces fonctions, à savoir:

Pour mieux situer ces fonctions, voici un diagramme d’état représentant la vie d’un thread POSIX.

pthread_state_diagram

Pour commencer, créer un programme qui démarre un thread affichant un message (passé en paramètre du thread)

Comme vous pourrez le voir, le message devra être passé en tant que pointeur const char* et être recasté dans la routine du thread reinterpret_cast<const char*>(message). La plupart du temps, c’est un objet qui est passé au thread pour permettre de passer plusieurs variables.

Tester tous les états possibles d’un thread en utilisant les fonctions données dans le diagramme d’état ci-dessus

Vous pourrez ainsi voir comment arrêter un thread en pleine exécution avec pthread_exit() ou pthread_cancel(). Et aussi voir comment laisser un thread vivre seul en utilisant pthread_detach().

De plus, avec la commande pthread_kill(), il vous sera possible de connaitre l’état du thread en envoyant un signal null. Ou bien d’envoyer un signal UNIX au thread (que vous devrez catcher dans le thread).

std::thread

Les threads de la standard library ont été implémentés depuis le C++11 en réutilisant l’implémentation réalisée par la librairie Boost. Leurs fonctionnements sont similaires aux threads de la librairie Pthread.

Adapter votre programme pour remplacer Pthread par l’objet std::thread de la standard library.

Vous noterez que cette API est moins complète que la librairie Pthread.

Sous Linux les process/thread possèdent des IDs qui leur sont propres:

Pour un thread toutes ces informations sont communes, car il n’y a qu’un processus. Néanmoins, pour faire la différence entre thread des fonctions existe pour obtenir l’ID d’un thread en cours d’exécution. L’ID obtenu est unique pour chaque thread d’un process. Mais peut-être le même qu’un autre thread d’un autre process.

Sous Linux la fonction système utilisé pour récupérer cet ID est : static_cast<pid_t>(syscall(SYS_gettid)). Il faudra inclure les librairies <unistd.h> et <sys/syscall.h> pour pouvoir l’utiliser.

Afficher l’ID de tout vos threads et de votre thread principal d’exécution, ainsi que leur PID…

N’hésitez pas à aller voir l’aide avec man ps. Cela vous donnera plus d’informations pour afficher chaque thread individuellement.

Nommage des threads

Il existe une fonctionnalité très intéressante de la librairie Pthread qui permet de nommer les threads. Cela peut être très utile pour faire du debuging ou du monitoring par thread. Pour nommer un thread utiliser la fonction pthread_setname_np(). La page man de cette fonction en plus de vous donner un exemple d’implémentation vous donnera aussi des méthodes pour afficher vos threads nommés.

Nommer vos threads et afficher les avec la commande ps. Afficher les infos CPU et RAM par thread.

Vous pourrez voir qu’en plus de fournir un moyen de debug par thread; Le nommage de vos threads vous donne aussi la possibilité de monitorer chaque thread de votre programme individuellement.

Limites système

Par défaut, un OS simule qu’un process peut prétendre à des ressources “infinies”. Mais physiquement le système est borné aux ressources dont il dispose. Pour éviter tout débordement, Linux possède des limites (qui peuvent être modifiées) pour les threads tel que :

Toutes les limites systèmes sont consultables à l’aide de la commande ulimit -a. Si vous essayer de franchir ces limites, les fonctions pour vous allouer les ressources vous retourneront des erreurs.

Tenter de franchir les limites de votre système en créant beaucoup de threads. Prenez soin d’afficher le retour de la fonction d’allocation de ressource pthread_create().

Dans certain cas il peut être nécessaire de modifier ces limites. Il faut néanmoins être prudent, au risque de faire planter la machine.

Partage mémoire entre threads

thread_memory_space

Au niveau mémoire, chaque processus possède un espace dans la mémoire centrale (main memory) qui lui est dédié. Aucun processus ne peut accéder à la mémoire d’un autre. Dans le cas où un processus essayerait d’accéder à un espace mémoire en dehors du sien, le système lui enverra un signal SIGSEGV.

Un thread lui, possède un emplacement mémoire privé uniquement pour les allocations sur la pile (stack). Toutes les variables globales, statiques, et les allocations sur le tas (heap) utilisent l’espace mémoire du processus qui a lancé le thread. Elles peuvent donc en tout temps être accédées par le processus ou par tous les threads de celui-ci. Il va de soi que deux threads dans deux processus distincts ne partage pas le même espace mémoire.

Pour mettre en évidence le partage des ressources entre thread, modifier le répertoire de travail avec un thread. Après quelques secondes avec un autre thread afficher le répertoire courant.

La fonction chdir() permet de changer le répertoire courant et getcwd() permet de l’afficher (voir les pages man pour leur utilisation).

Comme indiqué ci-dessus, le temps entre le changement de répertoire et son affichage est important. C’est le sujet du prochain TD sur le problème des accès concurrents.

Par Jérémy HERGAULT, le .