TP 3 - Algorithmique non-bloquante
Retour à la page de l'UE
Ce TP est à rendre. Envoyez une archive contenant votre compte-rendu et vos
fichiers source avant mardi prochain à 13h30, par e-mail à Jean-Pierre Lozi
(vous trouverez l'adresse sur le site de l'UE).
Exercice : une pile sans verrous
Le but de cet exercice est d'implémenter une pile sans verrou et de comparer
les performances de cette pile à celle d'une pile avec verrou. Pour cela, nous
allons implémenter la pile sans verrou proposée par Mellor-Crummey et Scott
(Synchronization Without Contention, publié à ASPLOS en 1991).
Question 1.
Dans un premier temps, vous allez créer un environnement dans lequel nous
pourrons tester nos piles. Dans ce premier exercice, vous devez créer N
threads, N étant un paramètre du programme. Chaque thread doit afficher le
message "Thread <N> starting..." puis se terminer. Ne fixez pas les
threads sur des cœurs.
Vous devez maintenant ajouter la structure suivante
struct node {
struct node *next;
int value;
};
à votre code et définir trois piles :
struct node *volatile stack_lock_free = 0;
struct node *stack_mutex = 0;
struct node *stack_clh = 0;
Question 2.
Implémentez les fonctions push_lock_free(int value) et
int pop_lock_free() permettant d'ajouter et de retirer
un entier dans la pile stack_lock_free. Pour cela,
utilisez l'algorithme sans verrou vu en cours, à ceci près que votre fonction
pop doit bloquer (attente active) tant qu'il n'y a pas d'élément dans la pile.
Remarque : Lorsque vous ajoutez un noeud, vous devez
préalablement allouer sa mémoire. On vous demande, dans cette question, de le
pas libérer la mémoire d'un noeud dans pop.
Vous testerez votre code en ajoutant et retirant 106 éléments dans
votre pile à partir de chaque thread (tous les ajouts avant tous les retraits).
Testez votre programme avec 10 threads.
Question 3.
On vous demande maintenant de libérer la mémoire des structures allouées dans
la fonction pop. Pour quelle raison votre programme plante-t-il? Expliquez
clairement le problème dans votre rapport.
Nous n'allons pas résoudre le problème pour l'instant, donc retirez votre
free().
Question 4. Nous allons maintenant créer
la version avec verrou de la pile. Implémentez les fonctions push_mutex(int value) et int pop_mutex() permettant d'ajouter et de retirer des éléments à
votre pile stack_mutex. Pour cela, utilisez un
algorithme avec verrouillage (pthread_mutex_lock()).
Ajoutez un paramètre à votre programme qui permettra de choisir l'une ou
l'autre des implémentations.
Testez votre fonction dans le thread en ajoutant et retirant 103
éléments dans votre file à partir de chaque thread.
Question 5.
En utilisant la fonction gettimeofday(), faîtes
quelques mesures. Quel algorithme est le plus efficace?
Question 6.
Utilisez maintenant l'algorithme CLH que vous avez implémenté au dernier TP
pour produire la troisième version de la pile. Comparez vos trois
implémentations avec un nombre variable de threads (ajustez le nombre
d'opérations pour obtenir des temps de mesure raisonnables). Bonus
: vous pouvez également utiliser des usleep(250) dans vos boucles d'attente active pour voir
comment cela affecte vos résultats.
Question 7.
Expliquez comment vous pourriez régler le problème de la question 1.3. On ne
vous demande pas d'implémenter votre solution.
Question 8.
Un compare-and-swap permet seulement de vérifier que la valeur d'un pointeur
est la même que précédemment, et non pas qu'elle n'a pas été modifiée entre
temps. Cela pose-t-il un problème pour votre pile non-bloquante ? Si oui,
comment le résoudre ?
Question 9 (pour les courageux).
Implémentez vos solutions des questions 7 et 8.