Mutex, variables de condition, deadlocks, initialisation, attributs et modèles d’utilisation
Ce cours présente les principaux mécanismes de synchronisation de POSIX Threads : les mutex et les variables de condition.
Plusieurs threads d’un même processus partagent le même espace d’adressage. Ils peuvent donc accéder aux mêmes variables globales, structures, fichiers et ressources. Sans synchronisation, leurs opérations peuvent s’entrelacer et produire des résultats incohérents.
Les API sont déclarées dans :
Code: Select all
#include <pthread.h>
Code: Select all
gcc programme.c -pthread -o programme
- en cas de succès ;
Code: Select all
0 - un numéro d’erreur positif en cas d’échec.
1. Conditions de concurrence
Une instruction C simple n’est pas forcément atomique.
Exemple :
Code: Select all
static volatile int glob = 0;
glob++;
- charger dans un registre ;
Code: Select all
glob - incrémenter cette copie ;
- réécrire le résultat dans .
Code: Select all
glob
- vaut 2000 ;
Code: Select all
glob - le thread A lit 2000 ;
- le thread A est interrompu ;
- le thread B incrémente la variable jusqu’à 3000 ;
- le thread A reprend et écrit 2001 ;
- les modifications du thread B sont perdues.
volatile ne protège pas les données
Le mot-clé
Code: Select all
volatile- l’atomicité ;
- l’exclusion mutuelle ;
- la cohérence entre threads ;
- l’absence d’interruption ;
- la protection d’un invariant partagé.
2. Sections critiquesÀ retenir :ne remplace jamais un mécanisme de synchronisation.Code: Select all
volatile
Une section critique est une portion de code qui accède à une ressource partagée et qui ne doit pas être exécutée simultanément par plusieurs threads.
Modèle général :
Code: Select all
verrouiller();
/* accès à la ressource partagée */
deverrouiller();
- déverrouillé : aucun thread ne le possède ;
- verrouillé : un thread en est propriétaire.
3. Type et initialisation d’un mutex
Le type POSIX est :
Code: Select all
pthread_mutex_t
Code: Select all
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
4. pthread_mutex_lock() et pthread_mutex_unlock()
pthread_mutex_lock()
Prototype :
Code: Select all
int pthread_mutex_lock(pthread_mutex_t *mutex);
Code: Select all
int s = pthread_mutex_lock(&mtx);
if (s != 0) {
errExitEN(s, "pthread_mutex_lock");
}
Prototype :
Code: Select all
int pthread_mutex_unlock(pthread_mutex_t *mutex);
Code: Select all
s = pthread_mutex_unlock(&mtx);
if (s != 0) {
errExitEN(s, "pthread_mutex_unlock");
}
Exemple complet :
Code: Select all
static int glob = 0;
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static void *
threadFunc(void *arg)
{
int loops = *((int *) arg);
for (int j = 0; j < loops; j++) {
int s = pthread_mutex_lock(&mtx);
if (s != 0)
errExitEN(s, "pthread_mutex_lock");
int loc = glob;
loc++;
glob = loc;
s = pthread_mutex_unlock(&mtx);
if (s != 0)
errExitEN(s, "pthread_mutex_unlock");
}
return NULL;
}
5. pthread_mutex_trylock()
Prototype :
Code: Select all
int pthread_mutex_trylock(pthread_mutex_t *mutex);
Valeurs importantes :
- : mutex acquis ;
Code: Select all
0 - : mutex déjà occupé.
Code: Select all
EBUSY
Code: Select all
int s = pthread_mutex_trylock(&mtx);
if (s == 0) {
/* section critique */
pthread_mutex_unlock(&mtx);
} else if (s == EBUSY) {
/* effectuer un autre travail */
} else {
errExitEN(s, "pthread_mutex_trylock");
}
Elle peut aussi éviter certains deadlocks : le thread verrouille un premier mutex, tente le second avec
Code: Select all
trylock()Cette stratégie peut toutefois produire un livelock si plusieurs threads se gênent continuellement sans progresser.
6. pthread_mutex_timedlock()
Prototype :
Code: Select all
int pthread_mutex_timedlock(
pthread_mutex_t *restrict mutex,
const struct timespec *restrict abs_timeout
);
En cas d’expiration :
Code: Select all
ETIMEDOUT
La date est représentée par :
Code: Select all
struct timespec {
time_t tv_sec;
long tv_nsec;
};
Code: Select all
struct timespec limit;
/* remplir limit avec une date absolue */
int s = pthread_mutex_timedlock(&mtx, &limit);
if (s == 0) {
/* mutex acquis */
pthread_mutex_unlock(&mtx);
} else if (s == ETIMEDOUT) {
/* délai expiré */
}
Sur Linux, NPTL utilise notamment les futexes. En l’absence de contention, un mutex peut souvent être acquis et libéré en espace utilisateur. Le noyau intervient surtout lorsqu’un thread doit être endormi ou réveillé.
Le coût dépend principalement :
- du nombre de threads en concurrence ;
- de la durée de la section critique ;
- du nombre de changements de contexte ;
- de la fréquence des verrouillages ;
- de la granularité choisie.
Un mutex unique protège une grande ressource.
Avantages :
- simple ;
- peu de risques de deadlock ;
- facile à maintenir.
Verrouillage fin
Plusieurs mutex protègent différentes parties d’une structure.
Avantages :
- meilleur parallélisme ;
- moins de contention sur un verrou unique.
- code plus complexe ;
- risque accru de deadlock ;
- gestion plus difficile de la durée de vie des verrous.
8. DeadlocksRègle pratique : un mutex protège une ressource ou un invariant partagé, pas seulement une variable isolée.
Un deadlock apparaît lorsque plusieurs threads attendent indéfiniment des ressources détenues les uns par les autres.
Exemple :
Code: Select all
/* Thread A */
pthread_mutex_lock(&mutex1);
pthread_mutex_lock(&mutex2);
/* Thread B */
pthread_mutex_lock(&mutex2);
pthread_mutex_lock(&mutex1);
Code: Select all
mutex1Code: Select all
mutex2Code: Select all
mutex2Code: Select all
mutex1Hiérarchie de verrouillage
La solution la plus simple est d’imposer un ordre global :
Code: Select all
mutex1 -> mutex2 -> mutex3
Code: Select all
pthread_mutex_lock(&mutex1);
pthread_mutex_lock(&mutex2);
Approche avec trylock()
Code: Select all
pthread_mutex_lock(&mutex1);
int s = pthread_mutex_trylock(&mutex2);
if (s == EBUSY) {
pthread_mutex_unlock(&mutex1);
/* attendre ou effectuer un autre travail */
}
9. Initialisation et destruction dynamiques
pthread_mutex_init()
Prototype :
Code: Select all
int pthread_mutex_init(
pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr
);
Code: Select all
pthread_mutex_t mtx;
int s = pthread_mutex_init(&mtx, NULL);
if (s != 0)
errExitEN(s, "pthread_mutex_init");
- est automatique ;
- est stocké dans une structure allouée dynamiquement ;
- doit utiliser des attributs particuliers.
Prototype :
Code: Select all
int pthread_mutex_destroy(pthread_mutex_t *mutex);
Code: Select all
int s = pthread_mutex_destroy(&mtx);
if (s != 0)
errExitEN(s, "pthread_mutex_destroy");
10. Attributs des mutex
Le type d’attributs est :
Code: Select all
pthread_mutexattr_t
Code: Select all
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
int pthread_mutexattr_settype(
pthread_mutexattr_t *attr,
int type
);
int pthread_mutexattr_gettype(
const pthread_mutexattr_t *restrict attr,
int *restrict type
);
Code: Select all
pthread_mutexattr_t attr;
pthread_mutex_t mtx;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(
&attr,
PTHREAD_MUTEX_ERRORCHECK
);
pthread_mutex_init(&mtx, &attr);
pthread_mutexattr_destroy(&attr);
11. Types de mutex
PTHREAD_MUTEX_NORMAL
Mutex rapide avec peu de vérifications.
Si un thread le verrouille deux fois :
Code: Select all
pthread_mutex_lock(&mtx);
pthread_mutex_lock(&mtx);
PTHREAD_MUTEX_ERRORCHECK
Ce type détecte certaines erreurs :
- double verrouillage par le même thread ;
- déverrouillage par un thread non propriétaire ;
- déverrouillage d’un mutex non verrouillé.
- : double verrouillage détecté ;
Code: Select all
EDEADLK - : mauvais propriétaire.
Code: Select all
EPERM
PTHREAD_MUTEX_RECURSIVE
Le même thread peut verrouiller le mutex plusieurs fois :
Code: Select all
pthread_mutex_lock(&mtx);
pthread_mutex_lock(&mtx);
Code: Select all
pthread_mutex_unlock(&mtx);
pthread_mutex_unlock(&mtx);
PTHREAD_MUTEX_DEFAULT
Ce type demande le comportement par défaut de l’implémentation. Il ne faut pas dépendre de vérifications particulières sans choisir explicitement un autre type.
Initialisateurs non portables
Certaines implémentations proposent :
Code: Select all
PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP
PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP
Code: Select all
_NPPour du code portable, utiliser :
Code: Select all
pthread_mutexattr_init();
pthread_mutexattr_settype();
pthread_mutex_init();
Un mutex protège une donnée, mais ne permet pas à lui seul d’attendre efficacement qu’un état devienne vrai.
Exemple de prédicat :
Code: Select all
avail > 0
Code: Select all
for (;;) {
pthread_mutex_lock(&mtx);
if (avail > 0)
break;
pthread_mutex_unlock(&mtx);
}
Le type est :
Code: Select all
pthread_cond_t
- une donnée partagée ;
- un prédicat ;
- un mutex protégeant ce prédicat.
13. Initialisation statiqueImportant : la variable de condition ne contient pas l’état. Elle transmet seulement une notification.
Code: Select all
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
Code: Select all
static pthread_mutex_t mtx =
PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond =
PTHREAD_COND_INITIALIZER;
static int avail = 0;
pthread_cond_signal()
Prototype :
Code: Select all
int pthread_cond_signal(pthread_cond_t *cond);
Code: Select all
pthread_cond_signal(&cond);
pthread_cond_broadcast()
Prototype :
Code: Select all
int pthread_cond_broadcast(pthread_cond_t *cond);
Code: Select all
pthread_cond_broadcast(&cond);
15. pthread_cond_wait()
Prototype :
Code: Select all
int pthread_cond_wait(
pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex
);
Code: Select all
pthread_mutex_lock(&mtx);
pthread_cond_wait(&cond, &mtx);
- la libération du mutex ;
- la mise en attente du thread ;
- l’attente d’une notification ;
- la reprise du mutex ;
- le retour au code appelant.
Code: Select all
pthread_cond_wait()Cette atomicité évite de perdre une notification entre la libération du verrou et la mise en attente.
16. pthread_cond_timedwait()
Prototype :
Code: Select all
int pthread_cond_timedwait(
pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime
);
En cas d’expiration :
Code: Select all
ETIMEDOUT
Même en cas de timeout, le mutex est repris avant le retour.
17. Toujours utiliser une boucle while
Modèle correct :
Code: Select all
pthread_mutex_lock(&mtx);
while (avail == 0) {
pthread_cond_wait(&cond, &mtx);
}
/* avail > 0 et mutex détenu */
avail--;
pthread_mutex_unlock(&mtx);
Code: Select all
ifCode: Select all
if (avail == 0) {
pthread_cond_wait(&cond, &mtx);
}
- un autre thread peut consommer la ressource avant le thread réveillé ;
- peut réveiller plusieurs threads ;
Code: Select all
pthread_cond_broadcast() - des réveils intempestifs sont autorisés ;
- un signal signifie seulement que l’état a peut-être changé.
18. Producteur-consommateurRègle essentielle : après chaque réveil, le thread doit revérifier le prédicat sous le mutex.
Producteur
Code: Select all
pthread_mutex_lock(&mtx);
avail++;
pthread_mutex_unlock(&mtx);
pthread_cond_signal(&cond);
Code: Select all
pthread_mutex_lock(&mtx);
avail++;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mtx);
Consommateur
Code: Select all
pthread_mutex_lock(&mtx);
while (avail == 0) {
pthread_cond_wait(&cond, &mtx);
}
avail--;
pthread_mutex_unlock(&mtx);
- le mutex protège ;
Code: Select all
avail - le prédicat décide si le consommateur peut continuer ;
Code: Select all
avail > 0 - la variable de condition évite une attente active.
Les fonctions :
Code: Select all
pthread_cond_wait()
pthread_cond_timedwait()
Si un thread est annulé pendant l’attente, le mutex est repris avant l’exécution des gestionnaires de nettoyage. Cela permet à un gestionnaire de libérer correctement le mutex.
20. Attendre la terminaison de n’importe quel thread
Code: Select all
pthread_join()
Code: Select all
pthread_join(thread_id, NULL);
Le chapitre construit ce mécanisme avec :
- un tableau décrivant les threads ;
- un état pour chaque thread ;
- un compteur de threads terminés mais non joints ;
- un mutex ;
- une variable de condition.
Code: Select all
enum tstate {
TS_ALIVE,
TS_TERMINATED,
TS_JOINED
};
Code: Select all
struct thread {
pthread_t tid;
enum tstate state;
int sleepTime;
};
Code: Select all
static pthread_mutex_t threadMutex =
PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t threadDied =
PTHREAD_COND_INITIALIZER;
static int numUnjoined = 0;
Code: Select all
pthread_mutex_lock(&threadMutex);
thread[index].state = TS_TERMINATED;
numUnjoined++;
pthread_mutex_unlock(&threadMutex);
pthread_cond_signal(&threadDied);
Code: Select all
pthread_mutex_lock(&threadMutex);
while (numUnjoined == 0) {
pthread_cond_wait(&threadDied, &threadMutex);
}
Code: Select all
TS_TERMINATED
Code: Select all
thread[index].state = TS_JOINED;
numUnjoined--;
Code: Select all
pthread_mutex_unlock(&threadMutex);
pthread_join(thread[index].tid, NULL);
21. Initialisation et destruction d’une variable de condition
pthread_cond_init()
Prototype :
Code: Select all
int pthread_cond_init(
pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr
);
Code: Select all
pthread_cond_t cond;
int s = pthread_cond_init(&cond, NULL);
if (s != 0)
errExitEN(s, "pthread_cond_init");
Prototype :
Code: Select all
int pthread_cond_destroy(pthread_cond_t *cond);
Code: Select all
int s = pthread_cond_destroy(&cond);
if (s != 0)
errExitEN(s, "pthread_cond_destroy");
22. Attributs des variables de condition
Le type est :
Code: Select all
pthread_condattr_t
Code: Select all
int pthread_condattr_init(pthread_condattr_t *attr);
int pthread_condattr_destroy(pthread_condattr_t *attr);
Code: Select all
pthread_condattr_t attr;
pthread_cond_t cond;
pthread_condattr_init(&attr);
pthread_cond_init(&cond, &attr);
pthread_condattr_destroy(&attr);
pthread_create()
Prototype :
Code: Select all
int pthread_create(
pthread_t *restrict thread,
const pthread_attr_t *restrict attr,
void *(*start_routine)(void *),
void *restrict arg
);
Code: Select all
pthread_t tid;
int s = pthread_create(
&tid,
NULL,
threadFunc,
&argument
);
Prototype :
Code: Select all
int pthread_join(
pthread_t thread,
void **retval
);
Code: Select all
pthread_join(tid, NULL);
24. Récapitulatif de toutes les API
Mutex
- : verrouille en attendant si nécessaire ;
Code: Select all
pthread_mutex_lock() - : libère le mutex ;
Code: Select all
pthread_mutex_unlock() - : tente sans attendre ;
Code: Select all
pthread_mutex_trylock() - : attend jusqu’à une date limite ;
Code: Select all
pthread_mutex_timedlock() - : initialise dynamiquement ;
Code: Select all
pthread_mutex_init() - : détruit le mutex.
Code: Select all
pthread_mutex_destroy()
- ;
Code: Select all
pthread_mutexattr_init() - ;
Code: Select all
pthread_mutexattr_destroy() - ;
Code: Select all
pthread_mutexattr_settype() - .
Code: Select all
pthread_mutexattr_gettype()
- : attend en libérant atomiquement le mutex ;
Code: Select all
pthread_cond_wait() - : attend avec une date limite ;
Code: Select all
pthread_cond_timedwait() - : réveille un thread ;
Code: Select all
pthread_cond_signal() - : réveille tous les threads ;
Code: Select all
pthread_cond_broadcast() - : initialise dynamiquement ;
Code: Select all
pthread_cond_init() - : détruit la variable de condition.
Code: Select all
pthread_cond_destroy()
- ;
Code: Select all
pthread_condattr_init() - .
Code: Select all
pthread_condattr_destroy()
- : crée un thread ;
Code: Select all
pthread_create() - : attend et récupère un thread.
Code: Select all
pthread_join()
Types :
Code: Select all
pthread_mutex_t
pthread_mutexattr_t
pthread_cond_t
pthread_condattr_t
pthread_t
Code: Select all
PTHREAD_MUTEX_INITIALIZER
PTHREAD_COND_INITIALIZER
Code: Select all
PTHREAD_MUTEX_NORMAL
PTHREAD_MUTEX_ERRORCHECK
PTHREAD_MUTEX_RECURSIVE
PTHREAD_MUTEX_DEFAULT
Code: Select all
PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP
PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP
- : mutex indisponible avec
Code: Select all
EBUSY;Code: Select all
trylock() - : délai expiré ;
Code: Select all
ETIMEDOUT - : deadlock détecté ;
Code: Select all
EDEADLK - : mauvais propriétaire ;
Code: Select all
EPERM - : objet ou paramètre invalide.
Code: Select all
EINVAL
Utiliser volatile comme verrou
Incorrect :
Code: Select all
volatile int glob;
glob++;
Code: Select all
pthread_mutex_lock(&mtx);
glob++;
pthread_mutex_unlock(&mtx);
Les lectures peuvent aussi participer à une race condition. Tous les accès liés au même invariant doivent respecter le même mutex.
Utiliser if au lieu de while
Incorrect :
Code: Select all
if (!predicate)
pthread_cond_wait(&cond, &mtx);
Code: Select all
while (!predicate)
pthread_cond_wait(&cond, &mtx);
Incorrect :
Code: Select all
avail++;
pthread_cond_signal(&cond);
Code: Select all
pthread_mutex_lock(&mtx);
avail++;
pthread_mutex_unlock(&mtx);
pthread_cond_signal(&cond);
Ne jamais détruire un mutex ou une variable de condition tant qu’un thread peut encore l’utiliser.
Garder un mutex trop longtemps
Éviter sous mutex :
- les entrées-sorties lentes ;
- les attentes réseau ;
- les calculs indépendants très longs ;
- les appels bloquants inutiles.
Correspondances conceptuelles :
- : proche de
Code: Select all
pthread_mutex_t, d’unCode: Select all
CRITICAL_SECTIONexclusif ou d’un objet Mutex selon le besoin ;Code: Select all
SRWLOCK - : proche de
Code: Select all
pthread_cond_t;Code: Select all
CONDITION_VARIABLE - : proche de
Code: Select all
pthread_cond_wait()ouCode: Select all
SleepConditionVariableCS();Code: Select all
SleepConditionVariableSRW() - : proche de
Code: Select all
pthread_cond_signal();Code: Select all
WakeConditionVariable() - : proche de
Code: Select all
pthread_cond_broadcast().Code: Select all
WakeAllConditionVariable()
Code: Select all
verrouiller
tester le prédicat dans while
attendre en libérant atomiquement le verrou
reprendre le verrou
modifier ou consommer l’état
déverrouiller
Observer l’ordonnancement
Modifier le programme d’incrémentation afin que chaque boucle affiche :
- la valeur de la variable globale ;
- un identifiant du thread.
Arbre binaire thread-safe
Implémenter :
Code: Select all
initialize(tree);
add(tree, key, value);
delete(tree, key);
lookup(tree, key);
Solution avancée : plusieurs mutex associés aux nœuds, avec une hiérarchie de verrouillage claire pour éviter les deadlocks.
29. Modèles essentiels
Protéger une donnée
Code: Select all
pthread_mutex_lock(&mtx);
/* accès à la donnée partagée */
pthread_mutex_unlock(&mtx);
Code: Select all
pthread_mutex_lock(&mtx);
while (!predicate) {
pthread_cond_wait(&cond, &mtx);
}
/* predicate vraie, mutex détenu */
pthread_mutex_unlock(&mtx);
Code: Select all
pthread_mutex_lock(&mtx);
modifier_etat_partage();
pthread_mutex_unlock(&mtx);
pthread_cond_signal(&cond);
Code: Select all
pthread_mutex_lock(&mtx);
modifier_etat_partage();
pthread_mutex_unlock(&mtx);
pthread_cond_broadcast(&cond);
Les mutex et les variables de condition résolvent deux problèmes complémentaires.
Le mutex protège les données partagées et garantit l’exclusion mutuelle. La variable de condition permet à un thread d’attendre efficacement qu’un prédicat devienne vrai.
Règles essentielles :
- ne pas utiliser comme mécanisme de synchronisation ;
Code: Select all
volatile - protéger tous les accès liés au même invariant avec le même mutex ;
- garder les sections critiques aussi courtes que possible ;
- toujours tester le prédicat dans une boucle ;
Code: Select all
while - modifier le prédicat sous mutex ;
- considérer un signal comme une invitation à revérifier l’état ;
- verrouiller plusieurs mutex dans un ordre constant ;
- ne détruire un objet que lorsqu’aucun thread ne peut encore l’utiliser.
Initialisateurs statiquesPrincipe fondamental : le mutex protège l’état partagé, la variable de condition annonce un changement potentiel et le prédicat décide si le thread peut continuer.
Code: Select all
PTHREAD_MUTEX_INITIALIZER
PTHREAD_COND_INITIALIZER
Code: Select all
PTHREAD_MUTEX_NORMAL
PTHREAD_MUTEX_ERRORCHECK
PTHREAD_MUTEX_RECURSIVE
PTHREAD_MUTEX_DEFAULT
Code: Select all
PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP
PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP
- : mutex indisponible avec
Code: Select all
EBUSY;Code: Select all
pthread_mutex_trylock() - : date limite atteinte ;
Code: Select all
ETIMEDOUT - : deadlock détecté avec certains types de mutex ;
Code: Select all
EDEADLK - : opération interdite, par exemple mauvais propriétaire ;
Code: Select all
EPERM - : objet ou paramètre invalide selon l’API.
Code: Select all
EINVAL
28.1 Utiliser volatile comme mécanisme de synchronisation
Incorrect :
Code: Select all
volatile int glob;
glob++;
Code: Select all
pthread_mutex_lock(&mtx);
glob++;
pthread_mutex_unlock(&mtx);
Si les écritures utilisent le mutex mais pas les lectures, une race condition peut toujours exister.
Tous les accès qui participent au même invariant doivent suivre le même protocole.
28.3 Attendre avec if au lieu de while
Incorrect :
Code: Select all
if (!predicate) {
pthread_cond_wait(&cond, &mtx);
}
Code: Select all
while (!predicate) {
pthread_cond_wait(&cond, &mtx);
}
Incorrect :
Code: Select all
avail++;
pthread_cond_signal(&cond);
Code: Select all
pthread_mutex_lock(&mtx);
avail++;
pthread_mutex_unlock(&mtx);
pthread_cond_signal(&cond);
Il ne faut pas appeler :
Code: Select all
pthread_mutex_destroy()
pthread_cond_destroy()
28.6 Verrouiller plusieurs mutex dans des ordres différents
Tous les threads doivent respecter une hiérarchie de verrouillage commune.
28.7 Garder un mutex trop longtemps
Une section critique doit rester aussi courte que possible, sans sacrifier la cohérence.
Il faut éviter d’effectuer sous mutex :
- des entrées-sorties lentes ;
- des attentes réseau ;
- des calculs indépendants très longs ;
- des appels susceptibles de bloquer inutilement.
Les mécanismes POSIX étudiés sont proches de ceux disponibles sous Windows.
- correspond conceptuellement à un mécanisme d’exclusion mutuelle comme
Code: Select all
pthread_mutex_t, unCode: Select all
CRITICAL_SECTIONexclusif ou, selon le besoin, un objet Mutex du noyau ;Code: Select all
SRWLOCK - correspond à
Code: Select all
pthread_cond_t;Code: Select all
CONDITION_VARIABLE - joue un rôle proche de
Code: Select all
pthread_cond_wait()ouCode: Select all
SleepConditionVariableCS();Code: Select all
SleepConditionVariableSRW() - correspond conceptuellement à
Code: Select all
pthread_cond_signal();Code: Select all
WakeConditionVariable() - correspond conceptuellement à
Code: Select all
pthread_cond_broadcast().Code: Select all
WakeAllConditionVariable()
Code: Select all
verrouiller
tester le prédicat dans while
attendre en libérant atomiquement le verrou
reprendre le verrou
modifier ou consommer l’état
déverrouiller
30.1 Observer l’ordonnancement des threads
Modifier le programme d’incrémentation concurrente afin que chaque itération affiche :
- la valeur actuelle de la variable globale ;
- un identifiant permettant de reconnaître le thread.
L’objectif est de visualiser que l’exécution des threads peut s’entrelacer de manière imprévisible.
30.2 Arbre binaire thread-safe
Implémenter des fonctions thread-safe opérant sur un arbre binaire non équilibré :
Code: Select all
initialize(tree);
add(tree, key, value);
delete(tree, key);
lookup(tree, key);
- utiliser un mutex global protégeant tout l’arbre ;
- solution simple et sûre ;
- parallélisme limité.
- associer des verrous aux nœuds ;
- autoriser plusieurs recherches simultanées ;
- gérer avec prudence les ajouts, suppressions et déplacements entre nœuds ;
- définir une hiérarchie de verrouillage pour éviter les deadlocks.
31.1 Protéger une donnée
Code: Select all
pthread_mutex_lock(&mtx);
/* lire ou modifier la donnée partagée */
pthread_mutex_unlock(&mtx);
Code: Select all
pthread_mutex_lock(&mtx);
while (!predicate) {
pthread_cond_wait(&cond, &mtx);
}
/* predicate est vraie et le mutex est détenu */
pthread_mutex_unlock(&mtx);
Code: Select all
pthread_mutex_lock(&mtx);
modifier_etat_partage();
pthread_mutex_unlock(&mtx);
pthread_cond_signal(&cond);
Code: Select all
pthread_mutex_lock(&mtx);
modifier_etat_partage();
pthread_mutex_unlock(&mtx);
pthread_cond_broadcast(&cond);
Les threads partagent facilement les données, mais cet accès concurrent doit être contrôlé.
Le mutex assure l’exclusion mutuelle et protège les invariants partagés. La variable de condition permet à un thread d’attendre efficacement qu’un état devienne vrai, sans gaspiller du temps processeur dans une boucle active.
Les règles essentielles sont :
- ne jamais utiliser comme remplacement d’un mécanisme de synchronisation ;
Code: Select all
volatile - protéger tous les accès liés au même invariant avec le même mutex ;
- réduire autant que possible la durée des sections critiques ;
- toujours tester le prédicat dans une boucle ;
Code: Select all
while - modifier le prédicat sous mutex ;
- considérer un signal comme une invitation à revérifier l’état ;
- verrouiller plusieurs mutex dans un ordre global constant ;
- ne détruire un mutex ou une variable de condition que lorsqu’aucun thread ne peut encore l’utiliser.
Principe fondamental : le mutex protège l’état partagé, la variable de condition annonce un changement potentiel, et le prédicat décide si le thread peut continuer.
