Exercice 1.
Initialisation de mutex
La bonne réponse est :
c
Copier le code
pthread_mutex_t mutex;
err=pthread_mutex_init(&mutex, NULL);
if(err!=0)
error(err,"pthread_mutex_init");
Explication :
pthread_mutex_t mutex; déclare un mutex.
pthread_mutex_init(&mutex, NULL); initialise ce mutex. Le deuxième argument
NULL signifie que le mutex est initialisé avec les attributs par défaut.
Exercice 2. Utilisation de pthread_mutex_lock() et pthread_mutex_unlock()
La bonne réponse est :
c
Copier le code
void update(int * val, pthread_mutex_t * mutex) {
err=pthread_mutex_lock(mutex);
if(err!=0)
error(err,"pthread_mutex_lock");
// mise à jour de la variable globale
err=pthread_mutex_unlock(mutex);
if(err!=0)
error(err,"pthread_mutex_unlock");
}
Explication :
La fonction pthread_mutex_lock() verrouille le mutex, garantissant ainsi qu'un seul
thread peut mettre à jour la variable partagée à la fois.
Ensuite, pthread_mutex_unlock() libère le mutex pour permettre à un autre thread
de l'utiliser.
Les autres propositions ont des erreurs dans l'ordre des appels (unlock avant lock, ou des
paramètres incorrects).
Exercice 3. Utilisation de plusieurs mutex
La bonne réponse est :
c
Copier le code
// thread A
update(&a,&b,&x,&y);
update(&a,&b,&x,&y);
// thread B
update(&b,&c,&y,&z);
update(&c,&a,&z,&x);
Explication :
L'ordre dans lequel les mutex sont verrouillés est crucial pour éviter les interblocages.
Dans cette réponse, les mutex sont verrouillés dans un ordre cohérent et bien défini, ce
qui empêche les conflits d'accès tout en évitant les interblocages.
Les autres réponses possèdent des appels à update dans des ordres qui pourraient provoquer
des blocages (deadlocks) ou des accès concurrentiels non protégés.
Exercice 4. Programme avec plusieurs threads
Explication du comportement :
Ce programme utilise quatre threads qui chacun incrémentent un million de fois la
variable global.
Le problème ici est qu'il n'y a pas de synchronisation (pas de mutex) pour protéger
l'accès à la variable partagée global. Cela provoque un comportement indéfini, car
plusieurs threads peuvent lire et écrire simultanément sur global, ce qui entraîne des
erreurs dans l'incrémentation (par exemple, des mises à jour perdues).
Exercice 5. Comparaison entre pthread_mutex_lock() et
pthread_mutex_trylock()
1. pthread_mutex_lock() :
o Avantages : Attente bloquante, assure que seul un thread accède à la section
critique.
o Inconvénients : Peut entraîner un blocage si un autre thread détient déjà le
mutex.
2. pthread_mutex_trylock() :
o Avantages : Ne bloque pas le thread si le mutex est déjà pris. Le thread peut
effectuer d'autres actions ou réessayer plus tard.
o Inconvénients : Moins efficace si utilisé de manière non appropriée. Peut
entraîner une boucle infinie si le thread continue à tenter de prendre le mutex
sans interruption.
Conclusion : pthread_mutex_lock() est plus simple et efficace pour la gestion de
l'exclusion mutuelle, mais pthread_mutex_trylock() peut être utile dans des cas où vous ne
voulez pas que le thread attende indéfiniment pour acquérir le mutex.
Exercice 6. Comportement de pthread_mutex_lock() et pthread_mutex_unlock()
1. Un thread exécute deux fois pthread_mutex_lock() sur le même mutex d'affilée :
o Cela entraînera une interblocage (deadlock), car un thread ne peut pas se
verrouiller deux fois sur le même mutex sans l'avoir libéré entre-temps.
2. Un thread exécute deux fois pthread_mutex_unlock() :
o Cela entraînera une erreur de type double unlock, ce qui peut provoquer un
comportement indéfini et entraîner un crash du programme (segmentation
fault) ou une erreur de synchronisation.
Exercice 7. Initialisation d'un sémaphore
La bonne réponse est :
c
Copier le code
sem_t semaphore;
sem_init(&semaphore, 0, 1);
// ...
sem_destroy(&semaphore);
Explication :
sem_init(&semaphore, 0, 1); initialise un sémaphore avec une valeur de 1. Le
deuxième argument 0 indique que le sémaphore est utilisé uniquement entre threads
dans le même processus.
sem_destroy(&semaphore); détruit le sémaphore après utilisation.
Les autres solutions présentent des erreurs d'allocation mémoire ou de valeurs initiales
incorrectes.
Exercice 8. Exclusion mutuelle avec des sémaphores
La bonne réponse est :
c
Copier le code
static sem_t semaphore;
long global = 0;
int increment(int i) {
// ...
}
void *inc(void * param) {
for(int j = 0; j < 1000000; j++) {
sem_wait(&semaphore);
global = increment(global);
sem_post(&semaphore);
}
}
int main(int argc, char *argv[]) {
pthread_t thread[NTHREADS];
int err;
sem_init(&semaphore, 0, 1); // Initialisation du sémaphore avec la
valeur 1
for (int i = 0; i < NTHREADS; i++) {
err = pthread_create(&(thread[i]), NULL, &inc, NULL);
if (err != 0)
error(err, "pthread_create");
}
// reste non fourni
}
Explication :
sem_wait(&semaphore) attend que le sémaphore soit disponible (valeur > 0), et
sem_post(&semaphore) libère le sémaphore en l'incrémentant.
Le sémaphore est initialisé à 1, ce qui permet à un seul thread d'entrer dans la section
critique à la fois.
Les autres solutions ont des erreurs de gestion du sémaphore ou d'initialisation incorrecte.
Exercice 9. Questions sur les sémaphores
1. Pourquoi sem_wait() prend un sem_t * ?
o sem_wait() prend un pointeur vers un sémaphore car elle modifie l'état
interne du sémaphore. Passer par référence permet à sem_wait de modifier
directement l'état du sémaphore.
2. Quand sem_init() peut-elle échouer ?
o sem_init() échoue si :
Le système a épuisé ses ressources pour les sémaphores.
Les arguments sont invalides (par exemple, un sem_t * nul).
Le sémaphore a déjà été initialisé.
3. Intérêt de sem_timedwait() ?
o sem_timedwait() permet de faire une opération wait() sur un sémaphore
avec un délai d'attente. Elle est utile si un thread ne veut pas bloquer
indéfiniment en attendant que le sémaphore soit disponible. Elle permet ainsi
de gérer le timeout dans les situations où une attente indéfinie n'est pas
acceptable.
Exercice 10 et 11. Producteur et Consommateur
Pour les deux exercices, la solution proposée présente un problème de synchronisation :
Le producteur attend le sémaphore empty avant de produire mais ne libère pas le
mutex avant d'insérer un élément.
Le consommateur attend le sémaphore full avant de retirer un élément, mais il ne
libère pas le mutex avant de le modifier.
Problème : La modification de la file (ou tampon) devrait être protégée par le mutex, mais la
gestion des sémaphores pour signaler les états du tampon est mal placée.
Il faut protéger les accès au tampon avec un mutex tout en utilisant les sémaphores pour gérer
le nombre d'éléments dans le tampon.
Exercice 12 et 13. Prédécesse et Rendez-vous entre deux threads
Ces exercices peuvent être résolus en utilisant des condvar (variables de condition) ou un
mécanisme de synchronisation basé sur des mutex et des sémaphores. Vous pouvez créer un
rendez-vous entre deux threads en synchronisant l'exécution de avrdv1() et avrdv2() avant
d'exécuter aprdev1() et aprdev2().