Deux verrous, deux ordres
Exercice · niveau 2 · informatique (MP2I/MPI), chapitre 22 — Concurrence et synchronisation
Énoncé
Deux fils prennent deux mutex, l'un dans l'ordre , l'autre dans l'ordre , cent mille fois. Que se passe-t-il ? Combien de temps cela met-il ?
Corrigé
Interblocage, cinq fois sur cinq, et il arrive vite : mesuré, le programme se bloque après à tours. Avec les deux fils prenant dans le même ordre : tours menés à bien, cinq fois sur cinq.
/* fil 1 */ /* fil 2 */
pthread_mutex_lock(&a); pthread_mutex_lock(&b); /* ordre INVERSE */
pthread_mutex_lock(&b); pthread_mutex_lock(&a);
/* ... */ /* ... */
L'entrelacement fatal tient en deux pas : le fil prend , le fil prend . Chacun réclame ensuite celui que l'autre détient, et aucun ne rendra le sien avant d'avoir obtenu le second. C'est le cycle d'attente du dîner des philosophes, réduit à deux convives.
Les cinquante tours méritent un commentaire. La probabilité de l'entrelacement fatal est faible à chaque tour — il faut que le fil prenne pendant l'intervalle, de quelques nanosecondes, où le fil détient sans avoir encore . Mais l'événement est répété, et une probabilité faible répétée assez souvent devient une certitude. En concurrence, un défaut improbable est un défaut qui arrivera — la seule question est de savoir en combien de tours.
Un cinquantième de cent mille : sur un service qui exécuterait cette boucle une fois par requête, l'incident surviendrait au bout d'une minute. Il n'y a pas de « suffisamment rare ».
Vérification exhaustive (problème 22.2) :
| états atteignables | états d'interblocage | |
|---|---|---|
| ordres inversés et | ||
| ordre total, des deux côtés |
Dix états, un seul mauvais : la faute est atteignable dans un cas sur dix, et le programme la trouve en cinquante essais. Le graphe entier tient sur une ligne, et pourtant le code ne le montre pas.
En pratique — Un ordre total sur les verrous, écrit une fois pour toutes
La parade est celle du cours, et elle est la seule qui passe à l'échelle : on numérote les verrous, et on les prend toujours par numéro croissant. Un cycle d'attente exigerait alors qu'un fil attende un verrou de numéro inférieur à celui qu'il détient, ce que le protocole interdit.
En pratique, l'ordre se documente à côté de la déclaration des verrous, comme un invariant :
/* ORDRE DE PRISE : verrou_comptes (1), puis verrou_journal (2).
JAMAIS l'inverse. */
pthread_mutex_t verrou_comptes;
pthread_mutex_t verrou_journal;
C'est une convention, non une vérification — rien ne l'impose au compilateur. C'est pourquoi elle s'écrit, et pourquoi le problème 22.2 existe.
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.