Portail informatique Année 2018 – 2019

PAAM – Programmation avancée des architectures multicoeurs

Écrivez en C un programme lançant 3 threads avec pthread_create. Chaque thread doit écrire sur le terminal [tid] Hello, World!!!tid est l'identifiant de thread retourné par pthread_self. Modifiez votre programme de façon à transmettre un paramètre entier i à chaque thread, où i est incrémenté à chaque création de thread. Modifiez aussi l'affichage effectué par le thread de façon à afficher ce paramètre plutôt que l'identifiant de thread. Ajoutez un affichage Main quitting à la fin de la fonction main. Lancez plusieurs fois votre programme. Pour quelle raison peut-il arriver que le Main quitting soit affiché avant le dernier Hello, World!!! ? Modifiez votre programme de façon à créer 20 threads. Pour quelle raison, souvent, ne voyez vous pas les 20 affichages effectués par les threads ? Ajoutez un appel à pthread_exit à la fin de la fonction main. Pour quelle raison voyez-vous maintenant les affichages effectués par les 20 threads ? En utilisant pthread_join, modifiez votre programme de façon à afficher forcément Main quitting après le dernier affichage à Hello, World!!!. Pour commencer, lancez 100 threads au lieu d'en lancer 20. Ensuite, ajoutez une variable globale entière cpt initialisée à 0 à votre programme. Dans chaque thread, incrémentez cet valeur après avoir affiché Hello, World!!! (le fait d'effectuer l'incrémentation après l'affichage sera important pour l'exercice suivant). Enfin, modifiez l'affichage Main quitting de façon à afficher la valeur du compteur à la fin du programme. Quelle est la valeur affichée et pour quelle raison ? Modifiez votre programme de façon à vous assurer que la valeur affichée soit bien 100. À la fin de chaque thread, ajoutez un affichage [i] Thread quittingi est le paramètre donné au thread. Modifiez votre programme de façon à ne lancer que 10 threads. On souhaite maintenant commencer les affichages Thread quitting après que chaque thread ait affiché Hello, World!!!. Pour cela, il suffit d'attendre que cpt ait atteint la valeur 10 avant d'effectuer le second affichage. Modifiez votre code en conséquence. Compilez votre programme avec l'option -O0 de gcc et vérifiez que votre code a un comportement correct. Compilez maintenant votre programme avec l'option -O3 de gcc. Pour quelle raison votre programme se gèle et ne termine jamais ? Ajoutez le mot clé volatile devant la définition de la variable cpt. D'après vous, quel est le rôle de ce mot clé ? Techniquement, nous venons de construire ce qu'on appelle une barrière de synchronisation : les threads se synchronisent à la fin de la phase d'affichage de Hello, World!!! avant de passer à la phase d'affichage de Thread quitting. Notre barrière est fonctionnelle mais elle a un énorme défaut : les threads utilisent inutilement de la ressource processeur lorsqu'ils sont en attente puisqu'ils passent leur temps à lire la variable cpt pour la comparer au nombre de threads attendus. En utilisant des variables conditions, modifiez votre programme de façon à endormir les threads tant que la variable cpt n'atteint pas le nombre de threads créés. Pour cela, vous devez utiliser pthread_cond_wait. Rappelez vous que cette appel doit se trouver dans une section critique protégée par un mutex. Pensez aussi à utiliser la fonction adéquate permettant de réveiller tous les threads lorsque cpt atteint la valeur attendue. On souhaite maintenant mettre en œuvre une file d'attente de message de type producteur/consommateur entre les threads. Techniquement, plusieurs threads doivent pouvoir déposer des messages et plusieurs threads doivent pouvoir les consommer. Modifiez votre programme de façon à afficher consommateur au lieu de Hello, World!!! si le paramètre du thread est inférieur à la moitié du nombre de thread. De façon similaire, modifiez votre programme pour afficher producteur sinon. Un message est défini par la structure suivante : struct message { int value; int sender; };

Pour stocker les messages entre leur émission et leur réception, nous définissons un tableau de 5 éléments de type struct message nommé sendbox. Il faut aussi définir deux variables int isend indiquant où, dans le tableau, le dernier message émit doit être placé et int irecv indiquant où, dans le tableau, le dernier message reçu doit être placé. Enfin, il faut définir combien de messages sont en attente. En effet, si isend est égal à irecv, il est impossible de savoir si sendbox est plein ou vide. Ajoutez ces définitions à votre programme.
Modifiez votre programme de façon à ce que :
  • Les émetteurs émettent en continue des messages. Si sendbox est pleine, un émetteur doit s'endormir sur une variable condition.
  • Les récepteurs lisent en continue les messages. Techniquement, nous construisons une communication unicast : un message n'est lu que par un récepteur. Si sendbox est vide, un récepteur doit s'endormir sur une variable condition. À la réception d'un message, un récepteur doit afficher l'expéditeur du message et le contenu du message.
Modifiez votre programme de façon à construire un canal de communication multicast : un message doit être lu par tous les récepteurs. N'hésitez pas à modifier la structure d'un message. Attention, il faut faire en sorte qu'un message soit reçu au plus une unique fois par chaque récepteur.
Félicitations, vous savez maintenant programmer en multi-threads !!!