informatique (MP2I/MPI) · Chapitre 22 · Application directe du cours
Attendre en tenant un verrou
Concurrence et synchronisation · niveau 2
Énoncé
Le cours avertit qu'il ne faut pas prendre le verrou avant d'attendre une place libre. Le mesurer sur un tampon de cases, un producteur, un consommateur, articles.
Corrigé
/* l'ordre du cours */ /* l'ordre inversé */
sem_wait(&vides); pthread_mutex_lock(&verrou);
pthread_mutex_lock(&verrou); sem_wait(&vides);
deposer(x); deposer(x);
pthread_mutex_unlock(&verrou); pthread_mutex_unlock(&verrou);
sem_post(&pleines); sem_post(&pleines);
Mesuré, trois exécutions de chaque :
| déposés | retirés | |
|---|---|---|
| sémaphore puis verrou | ||
| verrou puis sémaphore | , puis blocage |
Le est exactement la taille du tampon, et ce n'est pas un hasard. Voici la trace.
Le producteur remplit les deux cases sans difficulté — vides vaut puis puis . Au troisième tour, il prend le verrou, puis appelle sem_wait(&vides) sur un sémaphore à zéro : il s'endort en tenant le verrou. Le consommateur, qui pourrait libérer une case, commence par pthread_mutex_lock — et le verrou est pris. Les deux dorment pour toujours.
Ce que le zéro du consommateur ajoute : il n'a pas retiré un seul article. Le tampon est plein, il y aurait de quoi travailler, et il ne peut pas y accéder. L'interblocage n'est pas un manque de ressources : c'est un ordre de prise mal choisi.
Le sémaphore vides répond à la question « y a-t-il de la place ? » ; le verrou répond à « puis-je toucher au tampon ? ». La première ne concerne personne d'autre — attendre une place n'empêche aucun autre fil de progresser. La seconde bloque tout le monde.
D'où la règle générale : on acquiert les ressources de la moins bloquante à la plus bloquante, et on tient la plus bloquante le moins longtemps possible. Le verrou est pris juste avant l'écriture et rendu juste après — deux lignes, pas une de plus. Le sem_post(&pleines) lui-même est placé après le unlock, pour la même raison.