Vous êtes sur la page 1sur 3

TP 9: Concurrence Solène Mirliaz

TP 9 – Concurrence

I Compteur multithreadé
Dans cette section on souhaite réaliser un compteur multi-threadé. On étudie notamment le gain en performance
et les éventuelles limites du parallélisme.

I.1 Compteur mono-fil


On commence par écrire un compteur qui ne crée pas de fil d’exécution. Le programme effectuera donc son
travail uniquement avec le fil principal.

Q1. Dans un fichier compteur_simple.c, déclarer une variable globale compteur de type u_int64_t initialisée
à 0 et une constante objectif de même type valant 230 . Pour cela on pourra utiliser l’opérateur de décalage
binaire: 1 < < 30.

Q2. Écrire une fonction void *f(void* arg) qui incrémente la variable compteur via une boucle, objectif
fois.

Q3. Appeler f(NULL) quatre fois dans le main et afficher la valeur de compteur après cet appel (la chaîne de
formatage est %ld).

Q4. Compiler le programme. Utiliser la commande time ./compteur_simple pour mesurer le temps de calcul.
Selon votre machine, le temps d’exécution devrait être autour de 20s. Passer à un objectif de 231 s’il est
significativement plus petit.

I.2 Compteur quatre fils


On va maintenant répartir le travail sur quatre fils d’exécution, le fil principal attendant les fils de travail.
Copier-coller le fichier compteur_simple.c dans un nouveau fichier compteur_quatre.c.

Q5. Dans le main créer un tableau travailleurs de quatre fils d’exécution. Créer un fil d’exécution commençant
sur f pour chacune de ses cases. Après la création de tous les fils, utiliser pthread_join pour attester de
leur terminaison puis afficher le résultat du compteur global.

Q6. Si ce n’est pas déjà fait, protéger les accès au compteur global avec un mutex global mutex_compteur,
initialisé dans le main.

Q7. Comparer le temps d’exécution de ce nouveau programme.

Avec cette version multi-threadé, on ne réalise en réalité aucun gain en efficacité. En effet, les incréments sur
compteur ne se font pas en parallèle, à cause du mutex, et l’exécution doit en plus payer un petit changement de
contexte à chaque changement de fil d’exécution. Sur un grand nombre d’itérations, comme c’est le cas ici, on
perd alors beaucoup en performance.

Pour améliorer les performances on va utiliser des compteurs locaux pour chacun des fils. On cumulera
ensuite ce résultat dans compteur.

Q8. Dans la fonction f, initialiser une variable u_int64_t local à 0. Dans la boucle, incrémenter la variable
local au lieu de compteur. Aucun mutex n’est nécessaire. Après la boucle, ajouter le résultat local à
compteur, en protégeant compteur avec un verrouillage de mutex.

Q9. Compiler cette nouvelle version et observer le nouveau temps d’exécution.

MPI/MPI∗ - Lycée Thiers 1/ 3 2023/2024


TP 9: Concurrence Solène Mirliaz

II Tris parallèles
Dans cet section on souhaite comparer quatres algorithmes de tris avec une représentation graphique de chacun
d’entre eux, exécutés en parallèle.
On utilisera pour cela la bibliothèque raylib. La liste des fonctions est disponible à https://www.raylib.
com/cheatsheet/cheatsheet.html. Le fichier source et le Makefile pour démarrer le projet est disponible sur
Cahier-de-Prépas: TPs/TP9_tri.zip.

II.1 Mise en place


Q1. Télécharger le fichier sur Cahier-de-Prépas et vérifier sa bonne compilation avec make et exécution. Cela
crée un exécutable Tri. La compilation provoque des warning sur les fonctions de tris.

Q2. Pour l’instant les quatres tableaux ne sont pas initialisés. Utiliser la fonction GetRandomValue(a,b) pour
obtenir des valeurs aléatoires entre a et b (ou rand()). Ici on prendra pour chaque case une valeur entre 0
et 450. Dans le main, remplir t1, t2, t3 et t4 avec ces valeurs aléatoires. Pour un même indice, les quatre
tableaux doivent contenir la même valeur.

On veut maintenant créer un fil d’exécution par fonction de tri.

Q3. Déclarer quatre fils d’exécution dans le main et lancer-les sur chacune des fonctions de tri. On passera en
argument un tableau d’entiers à chaque tri au moment de la création du thread.

Q4. Compléter les fonctions de tris pour convertir leur argument de type void * vers une variable int *t.

Q5. Compléter chaque fonction de tris pour terminer le thread avec pthread_exit. On ne retournera aucune
valeur.

Q6. Compléter le main pour récupérer les fils d’exécution, après la boucle d’action principale.

Malgré l’inefficacité de certains des tris explorés durant ce TP, ceux-ci seront effectués trop vite pour que l’on
puisse apprécier leur déroulé. C’est pourquoi nous allons artificiellement ajouter des pauses grâce à la fonction
WaitTime(double seconds).

Q7. Définir deux variables globales lecture et ecriture de type double. Les initialiser à 0.001.

Q8. Lorsque la fenêtre se ferme (après la boucle while), affecter 0 à ces deux variables, avant les pthread_join
(le but est de rapidement terminer les tris lorsqu’on ferme la fenêtre).

La variable lecture représente le temps à attendre pour chaque lecture faite par l’algorithme et ecriture
celui pour chaque écriture. Par exemple l’opération echange entre deux cases de tableaux fait deux lectures et
deux écritures. On innsérera donc une instruction WaitTime(2*lecture + 2*ecriture) après chaque echange.
Vous êtes invités à modifier les valeurs de ces variables durant vos tests.

II.2 Tri sélection


Le tri sélection consiste à construire un tableau trié en allant chercher le plus petit élément dans la partie non-trié
du tableau. Il s’agit donc de deux boucles for imbriquées.

Q9. Compléter la fonction tri_selection pour trier le tableau reçu en argument selon cette méthode.

Q10. Tester l’exécution.

MPI/MPI∗ - Lycée Thiers 2/ 3 2023/2024


TP 9: Concurrence Solène Mirliaz

II.3 Tri Bulle


Le tri bulle sur un tableau de n éléments consiste à effectuer n passages sur le tableau, de gauche à droite, en
échangeant une case avec sa suivante dans le tableau si elles ne sont pas dans le bonne ordre. La première itération
va donc placer la plus grande valeur à sa place, à la case d’indice n − 1.
Cet algorithme a comme invariant qu’à l’itération i toutes les valeurs des indices n − i à n − 1 sont bien placées.
Par conséquence un tri bulle s’implémente avec deux boucles for imbriqués: la première (externe) décroit de
n − 1 à 1 et précise quelle section du tableau doit encore être triée, et la deuxième (interne) parcourt ce sous-tableau
en échangeant la case courante avec la suivante si elles ne sont pas dans le bonne ordre.
On n’implémentera pas l’optimisation qui consiste à vérifier si le tableau est trié avant d’arriver à la fin de la
boucle externe.

Q11. Compléter la fonction tri_bulle pour trier le tableau reçu en argument selon cette méthode.

Q12. Tester l’exécution.

II.4 Tri insertion


Le tri insertion consiste à garder une partie du tableau bien trié, par exemple des indices 0 à i à l’itération i, puis
de regarder l’indice i + 1 pour l’insérer à la bonne place. Pour cela on peut procéder avec une boucle while, qui,
partant de i + 1, va décroitre en faisant des permutations tant que l’ordre des éléments n’est pas correct.

Q13. Compléter la fonction tri_insertion pour trier le tableau reçu en argument selon cette méthode.

Q14. Tester l’exécution.

II.5 Tri rapide


Le tri rapide est un tri récursif. Dans le sous-tableau actuellement à trier, on sélectionne une valeur pivot (ici on
prendra la première ou dernière case du tableau). On sépare ensuite le reste du tableau entre les valeurs plus
petites que le pivot, à gauche, et celle plus grande, à droite. On retient l’indice qui sépare ces deux parties et on
y place le pivot. On appelle ensuite récursivement le tri rapidde sur les plus petites puis sur les plus grandes
valeurs.

Q15. Compléter la fonction tri_rapide pour trier le tableau reçu en argument selon cette méthode.

Q16. Tester l’exécution.

II.6 Améliorations
Q17. Tester les tris avec une valeur de lecture deux fois plus grande qu’ecriture. Observer les tris finissant en
premier. Inverser le rapport et recommencer l’expérience. Que conclure ?

Q18. En vous inspirant du code fournis ajouter deux nouveaux tris: le tri par tas (qui se fait bien en place) et le
tri fusion (qui ne doit pas se faire en place, il faudra écrire le résultat des fusions dans le tableau original
régulièrement pour voir le résultat).

MPI/MPI∗ - Lycée Thiers 3/ 3 2023/2024

Vous aimerez peut-être aussi