Un verrou par fil ne protège rien
Exercice · niveau 2 · informatique (MP2I/MPI), chapitre 22 — Concurrence et synchronisation
Énoncé
Quatre fils incrémentent fois un compteur partagé, chacun protégé par un mutex qu'il crée lui-même. Que mesure-t-on ?
void* travail(void* arg) {
pthread_mutex_t local; /* CHAQUE FIL LE SIEN */
pthread_mutex_init(&local, NULL);
for (int i = 0; i < 100000; i = i + 1) {
pthread_mutex_lock(&local);
compteur = compteur + 1;
pthread_mutex_unlock(&local);
}
pthread_mutex_destroy(&local);
return NULL;
}Corrigé
Mesuré, cinq exécutions : , , , , — pour attendus. Avec un mutex global partagé par les quatre fils : , cinq fois sur cinq.
Le verrou local ne protège absolument rien, et pourtant le code a l'air correct : il y a bien un lock, un unlock, et une section critique entre les deux. Le résultat est aussi faux que sans aucun verrou.
Pourquoi, en une phrase : un mutex ne protège pas du code, il coordonne des fils autour d'un objet. Quatre fils qui prennent quatre verrous différents ne s'attendent jamais. Chacun est seul à posséder le sien, chaque lock réussit immédiatement, et les quatre entrent ensemble dans la section critique.
En pratique — Le verrou doit être aussi partagé que la donnée qu'il protège
C'est la formulation générale, et elle se vérifie d'un coup d'œil : à côté de chaque déclaration de donnée partagée, on écrit quel verrou la protège, et ce verrou doit avoir exactement la même portée.
int compteur = 0; /* protégé par : verrou */
pthread_mutex_t verrou = PTHREAD_MUTEX_INITIALIZER;
Deux variantes du même défaut, à reconnaître :
- le verrou déclaré dans la fonction du fil — le cas ci-dessus ;
- le verrou passé par copie plutôt que par adresse. Copier une structure de verrou en duplique l'état : chaque fil verrouille sa copie. C'est le passage par valeur du chapitre chap:langage-c, et il est ici catastrophique. Un mutex ne se passe jamais autrement que par pointeur.
Un mot sur le nombre obtenu : à , soit un peu plus qu'à l'exercice 22.2 sans aucun verrou. Le verrou local ne synchronise rien, mais il consomme du temps, ce qui allonge chaque tour et change les entrelacements. Il rend le résultat un peu moins faux, et c'est ce qui le rend dangereux : sur un jeu de tests court, il pourrait passer.
Les autres exercices de ce chapitre Le cours du chapitre
Un blocage sur cet exercice ? Le tuteur d'Adloun guide par questions, sans donner la réponse.