Synchronisation des Threads et Sections Critiques
Synchronisation des Threads et Sections Critiques
Chapitre 6 (Silberchatz)
Module 5 1
Problèmes avec concurrence = parallélisme
Les threads concurrents doivent parfois partager
des données (fichiers ou mémoire commune) et
des ressources
On parle donc de tâches coopératives
Si l’accès n’est pas contrôlé, le résultat de
l’exécution du programme peut dépendre de
l’ordre d’entrelacement de l’exécution des
instructions (non-déterministe).
Un programme peut fournir différents résultats à
partir d’une exécution qui peuvent être parfois
indésirables.
Module 5 2
Exemple
Module 5 3
Vue globale d’une exécution possible
P1 P2
Interruption
M. Leblanc demande une ou retard
réservation d’avion
M. Guy demande une
réservation d’avion
Module 5 4
Deux opérations en parallèle sur une variable a
partagée (b est privé à chaque processus)
P1 interruption P2
b=a
b=a
b++
a=b
b++
a=b
Supposons que a soit 0 au début
P1 travaille sur le plus vieux a donc le résultat final sera a=1.
Sera a=2 si les deux tâches sont exécutées l’une après l’autre
Si a était sauvegardé quand P1 est interrompu, il ne pourrait pas être partagé avec P2 (il y aurait deux
a tandis que nous en voulons une seule)
Module 5 5
3ème exemple
Thread P1 Thread P2
Module 5 7
Section Critique
Module 5 8
Le problème de la section critique
Lorsqu’un thread manipule une donnée (ou ressource)
partagée, nous disons qu’il se trouve dans une section
critique (SC) (associée à cette donnée)
Le problème de la section critique est de trouver un
algorithme d`exclusion mutuelle de threads dans l’exécution
de leur SCs afin que le résultat de leurs actions ne
dépendent pas de l’ordre d’entrelacement de leur exécution
(avec un ou plusieurs processeurs)
L’exécution des sections critiques doit être mutuellement
exclusive: à tout instant, un seul thread peut exécuter une
SC pour une variable donnée (même lorsqu’il y a plusieurs
processeurs)
Ceci peut être obtenu en plaçant des instructions spéciales
dans les sections d`entrée et sortie
Pour simplifier les choses, assumons qu’il n’y a qu’une
seule SC dans un programme.
Module 5 9
Structure du programme
Chaque thread doit donc demander une permission avant
d’entrer dans une section critique (SC)
La section de code qui effectue cette requête est la section
d’entrée
La section critique est normalement suivie d’une section de
sortie
Le code qui reste est la section restante (SR): non-critique
repeat
section d’entrée
section critique
section de sortie
section restante
forever
Module 5 10
Application
X demande une
réservation d’avion
Section d’entrée
Section de sortie
Module 5 11
Critères nécessaires pour solutions valides
Exclusion Mutuelle:
À tout instant, au plus un thread peut être dans une
section critique (SC) pour une variable donnée
Progrès:
absence d’interblocage (Chap 7)
si un thread demande d`entrer dans une section critique
à un moment où aucun autre thread en fait requête, il
doit être en mesure d’y entrer
Non interférence:
Si un thread s’arrête dans sa section restante, ceci ne doit
pas affecter les autres threads
Mais on fait l’hypothèse qu’un thread qui entre dans une
section critique, en sortira.
Attente limitée (bounded waiting):
Aucun thread n’est empêché éternellement d’atteindre
sa SC (pas de famine)
Module 5 12
Types de solutions
Solutions par logiciel
des algorithmes dont la validité ne s’appuie pas sur
l’existence d’instruction spéciales
Solutions fournies par le matériel
s’appuient sur l’existence de certaines instructions (du
processeur) spéciales
Solutions fournies pas le SE
procure certains appels du système au programmeur
Toutes les solutions se basent sur l’atomicité de l’accès à la
mémoire centrale: une adresse de mémoire ne peut être
affectée que par une instruction à la fois, donc par un thread
à la fois.
Plus en général, toutes les solutions se basent sur
l’existence d’instructions atomiques, qui fonctionnent
comme des SCs de base
Atomicité = indivisibilité
Module 5 13
Solutions par logiciel
(pas pratiques, mais intéressantes pour comprendre le problème)
Notation
Débutons avec 2 threads: T0 et T1
Lorsque nous discutons de la tâche Ti, Tj dénotera
toujours une autre tâche différente (i != j)
Module 5 14
Algorithme 1: threads se donnent mutuellement le tour
La variable partagée turn est
initialisée à 0 ou 1
La SC de Ti est exécutée si
turn = i
Ti est occupé à attendre si Tj Thread Ti:
est dans SC.
Fonctionne pour l’exclusion
repeat
mutuelle! while(turn!=i){};
Pas de famine (seulement 1 SC
thread à son tour selon
turn). turn = j;
Mais le critère de progrès SR Rien
n’est pas satisfait car forever
l’exécution des SCs doit faire
strictement alterner
Module 5 15
initialisation de turn à 0 ou 1
while(turn!=0){}; while(turn!=1){};
SC SC
turn = 1; turn = 0;
SR SR
forever forever
Ex 2: Généralisation à n threads: chaque fois, avant qu’un thread puisse entrer dans sa
section critique, il lui faut attendre que tous les autres aient eu cette chance!
Module 5 16
Algorithme 2 ou l’excès de courtoisie...
Module 5 18
Algorithme 3 (dit de Peterson): bon!
Module 5 19
Entrer ou attendre?
Module 5 20
Thread T0: Thread T1:
repeat repeat
flag[0] = vrai; flag[1] = vrai;
// T0 veut entrer // T1 veut entrer
turn = 1; turn = 0;
// T0 donne une chance à T1 // T1 donne une chance à 0
while while
(flag[1]==vrai&&turn=1){}; (flag[0]==vrai&&turn=0){};
SC SC
flag[0] = faux; flag[1] = faux;
// T0 ne veut plus entrer // T1 ne veut plus entrer
SR SR
forever forever
Module 5 21
Scénario pour le changement de contrôle
Thread T0: Thread T1:
… …
SC flag[1] = vrai;
flag[0] = faux; // T1 veut entrer
// T0 ne veut plus entrer turn = 0;
SR // T1 donne une chance à T0
while
… (flag[0]==vrai&&turn=0){};
//test faux, entre
…
T1 prend la relève, donne une chance à T0 mais T0 a dit
qu’il ne veut pas entrer.
T1 entre donc dans la SC
Module 5 22
Autre scénario de changement de contrôle
Thread T0: Thread T1:
SC
flag[0] = faux;
// T0 ne veut plus entrer flag[1] = vrai;
SR // T1 veut entrer
flag[0] = vrai; turn = 0;
// T0 veut entrer // T1 donne une chance à T0
turn = 1; // mais T0 annule cette action
// T0 donne une chance à T1
while while
(flag[1]==vrai&&turn=1){}; (flag[0]==vrai&&turn=0){};
// test vrai, n’entre pas //test faux, entre
T0 veut rentrer mais est obligé de donner une chance à T1, qui entre
Module 5 23
Mais avec un petit décalage, c’est encore T0!
Thread T0:
Thread T1:
SC
flag[0] = faux;
// 0 ne veut plus entrer
RS
flag[0] = vrai;
// 0 veut entrer
flag[1] = vrai;
turn = 1;
// 1 veut entrer
// 0 donne une chance à 1
// mais T1 annule cette action
turn = 0;
// 1 donne une chance à 0
while while
(flag[1]==vrai&&turn=1){}; (flag[0]==vrai&&turn=0){};
// test faux, entre // test vrai, n’entre pas
Si T0 et T1 tentent simultanément d’entrer dans SC, seule une valeur de turn
survivra:
Module 5 non-déterministe (on ne sait pas qui gagnera), mais l’exclusion fonctionne 24
Donc cet algorithme n’oblige pas une tâche
d’attendre d’autres qui pourraient ne pas avoir besoin de la SC
Supposons que T0 soit le seul à avoir besoin de la SC, ou que T1 soit
lent à agir: T0 peut rentrer de suite (flag[1]==faux la dernière fois que T1
est sorti)
flag[0] = vrai // prend l’initiative
SC
Module 5 27
A propos d’échecs des threads
Module 5 28
Extension à >2 threads
Module 5 29
Une leçon à retenir…
À fin que des threads avec des variables
partagées puissent réussir, il est
nécessaire que tous les threads impliqués
utilisent le même algorithme de
coordination
Un protocole commun
Module 5 30
Critique des solutions par logiciel
Difficiles à programmer! Et à comprendre!
Les solutions que nous verrons dorénavant sont toutes
basées sur l’existence d’instructions spécialisées, qui
facilitent le travail.
Module 5 31
Solutions matérielles: désactivation des
interruptions
Sur un monoprocesseur:
l’exclusion mutuelle est
préservée mais l’efficacité se
détériore: Lorsqu’un Process Pi:
processus est dans sa SC il repeat
est impossible d’entrelacer
l’exécution d’autres inhiber interrupt
processus dans leur SR section critique
Perte d’interruptions rétablir interrupt
Dans un multiprocesseur: section restante
l’exclusion mutuelle n’est forever
pas garantie (long délais et
différentes horloges)
Une solution qui n’est
généralement pas acceptable
Module 5 32
Solutions matérielles: Instructions spécialisées
Module 5 33
L’instruction test-and-set
Un algorithme utilisant
Une version C++ de testset pour l’exclusion
test-and-set: Mutuelle:
La variable partagée b
bool testset(int& i) est initialisée à 0
{ Le 1er Ti/Pi qui met b à 1
if (i==0) { qui entre dans SC
i=1;
Tâche Ti/Pi:
return true; while testset(b)==false {};
} else { SC //entre quand vrai
return false; b=0;
} SR
}
Instruction atomique!
Module 5 34
L’instruction test-and-set (cont.)
Module 5 35
L’instruction ‘Échange’
Certains UCTs (ex: Pentium) offrent une
instruction xchg(a,b) qui interchange
atomiquement le contenue de a et b.
Mais xchg(a,b) souffre des même lacunes que
test-and-set
Module 5 36
Utilisation de xchg pour l’exclusion mutuelle
(Stallings)
Module 5 38
Sémaphores
Un sémaphore S est un entier qui, sauf durant l’initialisation,
est accessible seulement par ces 2 opérations atomiques et
mutuellement exclusives:
wait(S)
signal(S)
Module 5 39
Spinlocks d’Unix: Sémaphores occupés à attendre
(busy waiting)
Module 5 41
Atomicité et interruptibilité SC
atomique S--
SC
La boucle n’est pas atomique pour permettre à un autre thread
d’interrompre l’attente sortant de la SC
Module 5 42
Utilisation des sémaphores pour les sections
critiques
Pour n threads
Thread Ti:
Initialiser S à 1
repeat
Alors 1 seul thread peut wait(S);
être dans sa SC
SC
Pour permettre à k signal(S);
threads d’entrer dans leur SR
SC, initialiser S à k forever
Module 5 43
Initialise S à >=1
Module 5 44
Utilisation des sémaphores pour la
synchronisation de threads
On a 2 threads: T1 et Synchronisation
T2 correcte lorsque T1/P1
La commande S1 dans contient:
T1 doit être exécuté S1;
avant celle de S2 dans signal(S);
T2
Définissons un et que T2/P2 contient:
sémaphore S wait(S);
Initialisons S à 0 S2;
Module 5 45
Interblocage et famine avec les sémaphores
Famine: un thread peut ne jamais arriver à
exécuter s’il ne teste jamais le sémaphore
au bon moment
Inter-blocage: Supposons S et Q initialisés
à1
T0 T1
wait(S)
wait(Q)
wait(Q) wait(S)
Module 5 46
wait(S):
Sémaphores: observations while S<=0 {};
S--;
Quand S >= 0:
Le nombre de threads qui peuvent exécuter wait(S)
sans devenir bloqués = S
S threads peuvent entrer dans la SC
plus puissant que certains mécanismes déjà vus
dans les solutions où S peut être >1 il faudra avoir un 2ème sém.
pour les faire entrer un à la fois (exclusion mutuelle ou mutex)
Quand S est > 1, le thread qui entre le premier
dans la SC est le premier à tester S (choix
aléatoire)
ceci n’est plus vrai dans la solution suivante
Quand S < 0: le nombre de threads qui attendent
sur S est = |S| - Ne s’applique pas pour
sémaphores occupés à attendre
Module 5 47
Comment éviter l’attente occupée et le choix
aléatoire dans les sémaphores
Quand un thread doit attendre qu’un sémaphore
soit plus grand que 0, il est mis dans une file
d’attente de threads qui postulent sur le même
sémaphore.
Les files peuvent être PAPS (FIFO), avec priorités,
etc. Le SE contrôle l’ordre dans lequel les threads
sont choisis de la queue.
wait et signal deviennet donc des appels de
système comme les appels à des opérations
d’E/S.
Il y a une file d’attente pour chaque sémaphore
comme il y a une file d’attente pour chaque unité
d’E/S.
Module 5 48
Sémaphores sans attente occupée
Un sémaphore S devient une structure de données:
Une valeur (S.V)
Une liste d’attente L (S.L)
Un thread devant attendre un sémaphore S, est bloqué et
ajouté dans la file d’attente S.L du sémaphore (v. état bloqué =
attente chap 3).
Module 5 49
Implementation
(les boîtes réprésentent des séquences non-interruptibles)
Module 5 51
Problèmes classiques de synchronisation
Module 5 52
Le problème du producteur - consommateur
Module 5 53
Tampons de communication
Prod Prod
Cons Cons
in: 1ère
b[0] b[1] pos. libre b[0] b[1] b[2] b[3] b[4] b[5] b[6] b[7]
b[7] b[2]
b[6] b[3] ou
in: 1ère out: 1ère
b[5] b[4] pos. libre pos.
pleine
out: 1ère bleu: plein, blanc: libre
pos. pleine
Module 5 55
Pb de sync entre threads pour le tampon borné
Module 5 56
Sémaphores: rappel.
Soit S un sémaphore sur une SC
il est associé à une file d’attente
S positif: S threads peuvent entrer dans SC
S zéro: aucun thread ne peut entrer, pas de thread en attente
S négatif: |S| thread dans file d ’attente
Wait(S): S - -
si après S >= 0, thread peut entrer dans SC
si S < 0, thread est mis dans file d ’attente
Signal(S): S++
si après S<= 0, il y avait des threads en attente, et un thread
est réveillé
Indivisibilité = atomicité de ces opérations
Module 5 57
Solution avec sémaphores
Un sémaphore S pour l’exclusion mutuelle
de l’accès du tampon
Les sémaphores suivants ne font pas l’EM
Un sémaphore N pour synchroniser le
producteur et consommateur avec le
nombre d’éléments consommables dans le
tampon
Un sémaphore E pour synchroniser le
producteur et consommateur avec le
nombre d’espaces libres
Module 5 58
Solution de P/C: tampon circulaire fini de dimension k
Module 5
Sections critiques 59
Points importants à étudier
Module 5 60
Concepts importants de cette partie du Chap 6
Le problème de la section critique
L’entrelacement et l’atomicité
Problèmes de famine et interblocage
Solutions logiciel
Instructions matériel
Sémaphores occupés ou avec files
Fonctionnement des différentes solutions
L’exemple du tampon borné
Module 5 61
Quelques exemples
Module 5 62
Sémaphores: rappel
(les boîtes réprésentent des séquences non-interruptibles)
Module 5 64
Problème des lecteurs - rédacteurs
Plusieurs threads peuvent accéder à une
base de données
Pour y lire ou pour y écrire
Les rédacteurs doivent être synchronisés
entre eux et par rapport aux lecteurs
il faut empêcher à un thread de lire pendant
l’écriture
il faut empêcher à deux rédacteurs d ’écrire
simultanément
Les lecteurs peuvent y accéder
simultanément
Module 5 65
Une solution (n’exclut pas la famine)
Variable readcount: nombre de threads lisant la base de
données
Sémaphore mutex: protège la SC où readcount est mis à
jour
Sémaphore wrt: exclusion mutuelle entre rédacteurs et
lecteurs
Les rédacteurs doivent attendre sur wrt
les uns pour les autres
et aussi la fin de toutes les lectures
Les lecteurs doivent
attendre sur wrt quand il y a des rédacteurs qui écrivent
bloquer les rédacteurs sur wrt quand il y a des lecteurs qui
lisent
redémarrer les rédacteurs quand personne ne lit
Module 5 66
Les données et les rédacteurs
Rédacteur
wait(wrt);
. . .
// écriture
. . .
signal(wrt);
Module 5 67
Les lecteurs
wait(mutex);
readcount ++ ;
if readcount == 1 then wait(wrt);
signal(mutex);
Le premier lecteur d ’un groupe pourrait
devoir attendre sur wrt, il doit aussi
bloquer les rédacteurs. Quand il sera
//SC: lecture entré, les suivants pourront entrer
librement
wait(mutex);
readcount -- ;
if readcount == 0 then signal(wrt);
signal(mutex):
Module 5 69
Le problème des philosophes mangeant
5 philosophes qui
mangent et pensent
Pour manger il faut 2
fourchettes, droite et
gauche
On en a seulement 5!
Un problème classique
de synchronisation
Illustre la difficulté
d’allouer ressources aux
threads tout en évitant
interblocage et famine
Module 5 70
Le problème des philosophes mangeant
Un thread par
philosophe
Un sémaphore par Thread Pi:
fourchette: repeat
fork: array[0..4] of think;
semaphores wait(fork[i]);
wait(fork[i+1 mod 5]);
Initialisation: fork[i ] =1
eat;
for i:=0..4
signal(fork[i+1 mod 5]);
Première tentative: signal(fork[i]);
interblocage si chacun forever
débute en prenant sa
fourchette gauche!
Wait(fork[i])
Module 5 71
Le problème des philosophes mangeant
Une solution: admettre
seulement 4 philosophes
Thread Pi:
à la fois qui peuvent
repeat
tenter de manger
think;
Il y aura touj. au moins 1 wait(T);
philosophe qui pourra wait(fork[i]);
manger wait(fork[i+1 mod 5]);
même si tous prennent eat;
1 fourchette signal(fork[i+1 mod 5]);
Ajout d’un sémaphore T signal(fork[i]);
qui limite à 4 le nombre signal(T);
de philosophes “assis à forever
la table”
initial. de T à 4
Module 5 72
Avantage des sémaphores
(par rapport aux solutions précédentes)
Une seule variable partagée par section
critique
deux seules opérations: wait, signal
contrôle plus localisé (que avec les précéds)
extension facile au cas de plus. threads
possibilité de faire entrer plus. threads à la
fois dans une section critique
gestion de files d`attente par le SE: famine
évitée si le SE est équitable ([Link]. files
FIFO)
Module 5 73
Problème avec sémaphores: difficulté de programmation
Module 5 75
Moniteur
Caractéristiques:
variables locales accessibles seulement à
l’aide d’une procédure du moniteur
un thread entre dans le moniteur en invoquant
une de ses procédures
un seul thread peut exécuter dans le moniteur
à tout instant (mais plus. threads peuvent être en attente dans le
monit.)
Module 5 76
Moniteur
Il assure à lui seul l’exclusion mutuelle: pas
besoins de le programmer explicitement
Module 5 77
Structure générale du moniteur (style Java)
monitor nom-de-moniteur
{ // déclarations de vars
public entry p1(. . .) {code de méthode p1}
public entry p2(. . .) {code de méthode p2}
. . .
}
Module 5 78
Moniteur: Vue schématique simplifiée style Java
Module 5 79
Variables conditionnelles (n’existent pas en Java)
sont accessibles seulement dans le moniteur
accessibles et modifiables seulement à l’aide de 2
fonctions:
x: wait bloque l’exécution du thread exécutant sur la
condition x
le thread pourra reprendre l’exécution seulement si un
autre thread exécute x: signal)
x: signal reprend l’exécution d’un thread bloqué sur la
condition x
S’il en existe plusieurs: en choisir un (file?)
S’il n’en existe pas: ne rien faire
Module 5 80
Moniteur avec variables conditionnelles
Module 5 82
Retour au problème des philosophes mangeant
5 philosophes qui
mangent et pensent
Pour manger il faut 2
baguettes, droite et
gauche
On en a seulement 5!
Un problème classique
de synchronisation
Illustre la difficulté
d’allouer ressources aux
threads tout en évitant
interblocage et famine
Module 5 83
Philosophes mangeant structures de données
Chaque philos. a son propre state qui peut être
(thinking, hungry, eating)
philosophe i peut faire state[i] = eating ssi les voisins ne
mangent pas
Module 5 84
Chaque philosophe exécute à jamais:
repeat
pickup
eat
putdown
forever
Module 5 85
Un philosophe mange
private test(int i) {
if ( (state[(i + 4) % 5] != EATING) &&
(state[i] == HUNGRY) &&
(state[(i + 1) % 5] != EATING) ) {
state[i] = EATING;
self[i].signal;
} Un philosophe mange si ses voisins ne mangent pas et s’il
a faim.
}
Une fois mangé, il signale de façon qu’un autre pickup
soit possible, si pickup s’était arrêté sur wait
Module 5 Il peut aussi sortir sans avoir mangé si le test est faux 86
Chercher de prendre les baguettes
test((i + 4) % 5);
test((i + 1) % 5);
} Une fois fini de manger, un philosophe se
préoccupe de faire manger ses voisins en les
testant
Module 5 88
Relation entre moniteurs et autre mécanismes
Module 5 89
Le problème de la SC en pratique...
Les systèmes réels rendent disponibles
plusieurs mécanismes qui peuvent être
utilisés pour obtenir la solution la plus
efficace dans différentes situations
Module 5 90
Concepts importants du Chapitre 6
Sections critiques: pourquoi
Difficulté du problème de la synch sur SC
Bonnes et mauvaises solutions
Accès atomique à la mémoire
Solutions logiciel `pures`
Solution matériel: test-and-set
Solutions par appels du système:
Sémaphores, moniteurs, fonctionnement
Problèmes typiques: tampon borné,
lecteurs-écrivains, philosophes
Module 5 91