Vous êtes sur la page 1sur 17

Chapitre 3 :

La synchronisation de processus

1
Chapitre 3 : La synchronisation de processus

Introduction
Nous avons déjà dit que, si les processus étaient des entités autonomes et indépendantes, ils
pouvaient se trouver en conflit pour l'accès à certaines ressources communes. Ceci nécessite la
mise en œuvre de mécanismes dits de synchronisation pour gérer ces conflits. Par ailleurs, si
les processus sont en général indépendants, cela ne doit pas interdire la communication. La
conséquence de ces mécanismes est le blocage des processus en attente d'une ressource
momentanément indisponible. Deux problèmes en découlent : l'interblocage et la famine.

1. Mécanismes de synchronisation
Considérons un compte bancaire, dont le montant est mémorisé dans un emplacement donné A
sur disque. Le programme qui consiste à ajouter 100 à ce compte pourrait être le suivant, où N
est une variable locale du programme :
Lire (N, A);
N := N + 100;
Écrire (N, A);

Si maintenant deux processus différents consistent à exécuter ce même programme, le


processeur sera alloué à chacun d'eux dans un ordre quelconque. En particulier, on peut
imaginer que l'ordre soit le suivant:
Processus P1 Processus P2
Lire (N, A);
Lire (N, A);
N := N + 100;
Écrire (N, A);
N := N + 100;
Écrire (N, A);

N étant une variable locale du programme, implique qu'il en existe un exemplaire pour chacun
des deux processus.

Il s'ensuit que, si la valeur initiale du compte est 1000, on constate qu'après ces exécutions, il
est de 1100 au lieu de 1200. Les deux processus ne sont pas en fait totalement indépendants. Ils
partagent la ressource commune qu'est la valeur du compte bancaire à l'adresse A sur disque.
Cette ressource doit être à un seul point d'accès, c'est donc une ressource critique, et les
processus sont en exclusion mutuelle sur cette ressource critique.

2
Si la variable N est commune aux deux processus, le problème n'est pas résolu pour autant. En
effet l'instruction N := N + 100 n'est pas une instruction simple. En fait elle est décomposée
en trois instructions de la machine qui sont :
LOAD N
ADD 100
STORE N

L'allocateur du processeur prend en compte les instructions de la machine et non les instructions
du langage évolué pour déterminer les moments où il peut remplacer le processus actif par un
autre. En particulier, dans cet exemple, il est possible que le changement ait lieu juste après la
première instruction, donnant la séquence suivante :
Processus P1 Processus P2
LOAD N
LOAD N
ADD 100
STORE N
ADD 100
STORE N

Comment éviter ces problèmes ?

Le problème provient du fait que le processus P2 a utilisé une variable partagée avant que P1
ait fini de s'en servir. Si l'on arrive à bien synchroniser les processus, le problème peut être
résolu.

Lorsqu'un processus accède à la variable IN, les autres processus ne doivent ni la lire, ni la
modifier jusqu'à ce que le processus ait terminé de la mettre à jour. D'une manière générale, il
faut empêcher les autres processus d'accéder à un objet partagé si cet objet est en train d'être
utilisé par un processus, ce qu'on appelle d'assurer l'exclusion mutuelle. Les situations de ce
type, où deux ou plusieurs processus lisent ou écrivent des données partagées et où le résultat
dépend de l'ordonnancement des processus, sont qualifiées d'accès concurrents.

2. Sections critiques
On appelle section critique la partie d'un programme où la ressource est seulement accessible
par le processus en cours. Il faut s'assurer que deux processus n'entrent jamais en même temps
en section critique sur une même ressource. A ce sujet, aucune hypothèse ne doit être faite sur
les vitesses relatives des processus.

3
Exemple : la mise à jour d'un fichier (deux mises à jour simultanées d'un même compte client).
La section critique comprend :

- lecture du compte dans le fichier,

- modification du compte,

- réécriture du compte dans le fichier.

Le problème de conflit d'accès est résolût, si on peut assurer que deux processus ne soient jamais
en section critique en même temps au moyen de l'exclusion mutuelle. En général, les processus
qui exécutent des sections critiques sont structurés comme suit :

- Section non critique.

- Demande d'entrée en section critique.

Si la section critique est non libre => Attente

➢ Attente Active : la procédure entrer_Section_Critique est une boucle dont la condition


est un test qui porte sur des variables indiquant la présence ou non d’un processus en
Section critique.

➢ Attente Passive : le processus passe dans l’état endormi et ne sera réveillé que lorsqu’il
sera autorisé à entrer en section critique.

- Section critique.
- Demande de sortie de la section critique.
- Section non critique.
Quatre conditions sont nécessaires pour réaliser correctement une exclusion mutuelle :

1. Deux processus ne peuvent être en même temps en section critique.


2. Aucune hypothèse ne doit être faite sur les vitesses relatives des processus et sur le
nombre de processeurs.
3. Aucun processus suspendu en dehors d'une section critique ne doit bloquer les autres
processus : Interblocage
4. Aucun processus ne doit attendre trop longtemps avant d'entrer en section critique :
famine
La première condition est satisfaisante pour éviter le conflit d’accès. Cependant, elle ne suffit
pas pour garantir le bon fonctionnement du système d’exploitation, en particulier en ce qui
concerne l’égalité d’accès aux sections critiques.

4
Il existe de nombreux mécanismes pour mette en œuvre l’exclusion mutuelle, dont chacun a ses
avantages et inconvénients :

2.1. Algorithmes par attente Active


2.1.1. Masquage des interruptions
Le processus qui entre en section critique masque les interruptions et ne peut donc plus être
désalloué (exemple : accès du système d’exploitation a la table des processus).

Mais s'il y a plusieurs processeurs ou si le processus "oublié" de démasquer les interruptions, il


y a problème !!!

Cette solution est dangereuse, car si le processus, pour une raison ou pour une autre, ne restaure
pas les interruptions à la sortie de la section critique, ce serait la fin du système. La solution
n'assure pas l'exclusion mutuelle si le système n'est pas monoprocesseur, car le masquage des
interruptions concernera uniquement le processeur qui a demandé l'interdiction. Les autres
processus exécutés par un autre processeur pourront donc accéder aux objets partagés.

2.1.2. Variables de verrouillage


Une autre tentative pour assurer l'exclusion mutuelle est d'utiliser une variable de verrouillage
partagée verrou, unique, initialisée à 0. Pour rentrer en section critique un processus doit tester
la valeur du verrou. Si elle est égale à 0, le processus modifie la valeur du verrou à 1 et exécute
sa section critique. À la fin de la section critique, il remet le verrou à 0. Sinon, il attend (par une
attente active) que le verrou devienne égal à 0.
Algorithme1 Verrouillage
while verrou != 0 do;
// Attente active
end while
verrou <- 1
Section_critique() ;
Verrou <- 0

Problèmes Cette méthode n'assure pas l'exclusion mutuelle : Supposons qu'un processus est
suspendu juste après avoir lu la valeur du verrou qui est égal à 0. Ensuite, un autre processus
est élu. Ce dernier teste le verrou qui est toujours égal à 0, met verrou à 1 et entre dans sa section
critique. Ce processus est suspendu avant de quitter la section critique. Le premier processus
est alors réactivé, il entre dans sa section critique et met le verrou à 1. Les deux processus sont
en même temps en section critique.

5
2.1.3. Utilisation de primitives (SLEEP et WAKEUP)
La primitive SLEEP() est un appel système qui suspend l'appelant en attendant qu'un autre le
réveille. WAKEUP(processus) est un appel système qui réveille un processus. Par exemple,
un processus H qui veut entrer dans sa section critique est suspendu si un autre processus B est
déjà dans sa section critique. Le processus H sera réveillé par le processus B, lorsqu'il quitte la
section critique.

Problème : Si les tests et les mises à jour des conditions d'entrée en section critique ne sont pas
exécutés en exclusion mutuelle, des problèmes peuvent survenir. Si le signal émis par
WAKEUP() arrive avant que le destinataire ne soit endormi, le processus peut dormir pour
toujours.

2.2. Algorithmes par attente Passive


2.2.1. Sémaphores
La résolution des problèmes multitâches a considérablement progressé avec l'invention des
sémaphores en 1965. Il s'agit d'un outil puissant, facile à implanter et à utiliser.

Les sémaphores permettent de résoudre un grand nombre de problèmes liés à la programmation


simultanée, notamment le problème de l'exclusion mutuelle.

Un sémaphore est une structure à deux champs :

- une variable entière, ou valeur du sémaphore. Un sémaphore est dit binaire si sa valeur ne peut
être que 0 ou 1, général sinon.

- une file d'attente de processus ou de taches.

Dans la plupart des cas, la valeur du sémaphore représente à un moment donné le nombre
d'accès possibles à une ressource (section critique).

Seules deux fonctions permettent de manipuler un sémaphore :

- DOWN(s) ou P(s) ou WAIT(s)

- UP(s) ou V(s) ou SIGNAL(s)

L'opération DOWN(S) décrémente la valeur du sémaphore S si cette dernière est supérieure à


0. Sinon le processus appelant est mis en attente. Le test du sémaphore, le changement de sa
valeur et la mise en attente éventuelle sont effectués en une seule opération atomique
indivisible.

6
L'opération UP(S) incrémente la valeur du sémaphore S, si aucun processus n'est bloqué par
l'opération DOWN(S). Sinon, l'un d'entre eux sera choisi et redeviendra prêt. UP(S) est aussi
une opération indivisible.
Algorithmes:
Semaphore mutex = 1;
Processus P1: Processus P2:

DOWN(mutex) DOWN(mutex)

Section critique de P1; Section critique de P2;

UP(mutex); UP(mutex);

Exemple : Parking N places contrôlé par un feu.

Fig. 1 : Parking N places contrôlé par un feu

- Etat initial : parking vide … valeur sémaphore =N

- Capteur entrée = rôle de P (si S > = 0 feu vert, si non rouge)

- Capteur de sortie = rôle de V (si S < = 0 feu vert pour une voiture)

Algorithmes:
Initialisation(sémaphore,n)
valeur[sémaphore] = n

- P(sémaphore)
valeur[sémaphore] = valeur[sémaphore] - 1
si (valeur[sémaphore] < 0) alors
étatProcessus = Bloqué
mettre processus en file d’attente
finSi
invoquer l’ordonnanceur

- V(sémaphore)

7
valeur[sémaphore] = valeur[sémaphore] + 1
si (valeur[sémaphore] == 0) alors
extraire processus de file d’attente
étatProcessus = Prêt
finSi
invoquer l’ordonnanceur

Les sémaphores permettent d’élaborer des mécanismes de plus haut niveau et de résoudre les
problèmes de synchronisation typiques :

- Exclusion mutuelle : Quand Processus1 s’exécute sans que Processus2 soit


activé, alors P(mutex) fait passer mutex à 0 et Processus1 entre en section critique. Si
Processus2 est activé quand Processus1 en section critique, alors P(mutex) fait passer
mutex à –1 . Donc Processus2 se retrouve bloqué. Quand Processus1 exécute V(mutex),
alors mutex passe à 0 et Processus2 est débloqué.

- Cohorte : permet la coopération d’un groupe de taille max donnée.

- Passage de témoins : permet la coopération par répartition du travail entre


processus. Ce mécanisme peut être utilisé par Processus1 pour donner à Processus2 le
droit d’accès à une ressource quand Processus1 estime que cette ressource est prête ou
pour permettre à deux processus de s’échanger des informations à un moment précis de
leur exécution avant de continuer.

2.2.2. Moniteurs
L’idée des moniteurs est d’offrir le support des exclusions mutuelles au niveau du langage. Un
moniteur est constitué d’un ensemble de données et de procédures regroupées dans un module
ayant comme propriété qu’un seul processus peut être actif dans le moniteur à un instant donné.
Moniteur file {
Int compteur = 0 ;

Procédure mettre (Objet o) {
}

Procédure retirer (Objet o) {
}
}

C’est le compilateur qui gère l’exclusion mutuelle au sein du moniteur et non plus le
programmeur, d’où le risque d’erreur est bien plus faible.

8
Une implémentation effective des moniteurs a été réalisée dans le langage Java au moyen de
qualificateur synchronized, et des méthodes wait et notify.

3. Problèmes classiques de communication interprocessus


Les processus ont besoin de communiquer, d'échanger des informations de façon plus élaborée
et plus structurée que par le biais d'interruptions.

1.1. Problème des producteurs et des consommateurs


Le producteur doit pouvoir ranger en zone commune des données qu'il produit en attendant que
le consommateur soit prêt à les consommer. Le consommateur ne doit pas essayer de
consommer des données inexistantes. On suppose que les données sont de taille constante, et
les vitesses respectives des deux processus (producteur et consommateur) sont quelconques.

Règle 1 : Le producteur ne peut pas ranger un objet si le tampon est plein.

Règle 2 : Le consommateur ne peut pas prendre un objet si le tampon est vide.

Règle 3 : Exclusion mutuelle au niveau de l'objet : le consommateur ne peut pas prélever un


objet que le producteur est en train de ranger.
PRODUCTEUR CONSOMMATEUR

Faire toujours Faire toujours

produire un objet si nb d'objets dans tampon >0 alors

si nb d'objets dans tampon < N alors prendre l'objet

déposer l'objet dans le tampon consommer l'objet

finsi finsi

Fait Fait

Règle 4 : Si le producteur est en attente parce que le tampon est plein, il doit être averti des que
cette condition cesse d'être vraie. De même, si le consommateur est en attente parce que le
tampon est vide, il doit être averti des que cette condition cesse d'être vraie.

Le tampon peut être représenté par une liste circulaire. On introduit donc deux variables
caractérisant l'état du tampon :

- NPLEIN : nombre d'objets dans le tampon (début : 0)

- NVIDE : nombre d'emplacements disponibles dans le tampon (début : N).

PRODUCTEUR : CONSOMMATEUR :

9
Faire toujours Faire toujours
Produire un objet si NPLEIN > 0
si NVIDE >0 alors NPLEIN <- NPLEIN - 1
alors NVIDE <- NVIDE - 1 sinon s'endormir
sinon s'endormir finsi
finsi prelever l'objet dans le
tampon
ranger l'objet dans le tampon
si producteur endormi
si consommateur endormi
alors reveiller le
alors reveiller le
producteur
consommateur
sinon NVIDE <- NVIDE + 1
sinon NPLEIN <- NPLEIN + 1
finsi
finsi
consommer l'objet
Fait
Fait

Solution avec les sémaphores : On considère NVIDE et NPLEIN des sémaphores :

PRODUCTEUR CONSOMMATEUR

Faire toujours Faire toujours


produire un objet P(NPLEIN)
P(NVIDE) prendre l'objet
déposer un objet dans le tampon V(NVIDE)
V(NPLEIN) consommer l'objet
Fait Fait

On démontre que le producteur et le consommateur ne peuvent jamais être bloqués


simultanément.

Activité : Traiter le cas où le nombre de producteur et/ou consommateur est supérieur à 1.

1.2. Problème des philosophes


Il s'agit d'un problème très ancien qui modélise bien les processus en concurrence pour accéder
à un nombre limité de ressources. "5 philosophes sont assis autour d'une table ronde. Chaque
philosophe a devant lui une assiette de spaghettis si glissants qu'il lui faut deux fourchettes pour
les manger. Or, il n'y a qu'une fourchette entre deux assiettes consécutives. L'activité d'un
philosophe est partagée entre manger et penser. Quand un philosophe a faim, il tente de prendre
les deux fourchettes encadrant son assiette. S'il y parvient, il mange, puis il se remet à penser.
Comment écrire un algorithme qui permette à chaque philosophe de ne jamais être bloqué ? "

10
Fig. 2 : Problème des philosophes

Il faut tout à la fois éviter les situations :

- d'interblocage : par exemple tous les philosophes prennent leur fourchette


gauche en même temps et attendent que la fourchette droite se libère.

- de privation : tous les philosophes prennent leur fourchette gauche, et, constatant
que la droite n'est pas libre, la reposent, puis prennent la droite en même temps, etc...

1.3. Problème du coiffeur endormi


Le problème du coiffeur endormi a pour cadre un salon de coiffure. Un coiffeur tient un salon
où l'on trouve n chaises d'attente et un fauteuil. Lorsque le coiffeur n'a pas de clients, il s'assoie
dans le fauteuil et s'endort. Lorsqu'un client arrive, il doit réveiller le coiffeur pour se faire
coiffer. Si durant ce temps d'autres clients arrivent, ils s'assoient sur une chaise d'attente s'il en
reste. Sinon, ils vont encourager la concurrence. Ce problème modélise d'une certaine façon
une file d'attente de taille fini

11
Fig. 3 : Problème du coiffeur endormi

1.4. Problème des lecteurs et des rédacteurs


Il s'agit d'un autre problème classique qui modélise bien les accès d'un processus à une base de
données. On peut accepter que plusieurs processus lisent la base en même temps. Mais, dès
qu'un processus écrit dans la base, aucun autre processus ne doit être autorisé à y accéder, en
lecture ou en écriture.

4. Interblocage
Le modèle le plus commun d’opérations d’un système informatique est celui de processus qui doivent
partager l’accès à des ressources (UC, Mémoires, périphériques, …). Les problèmes d’accès à ces
ressources pouvaient être résolus par des stratégies de synchronisation faisant appel à des sémaphores
ou des moniteurs par exemple. Il est possible d’empêcher les processus d’accéder au même temps à une
ressource.

Même la meilleure technique de synchronisation ne peut toutefois garantir que chaque processus aura
accès aux ressources dont il a besoin et que d’autres processus ne restent pas éternellement bloqués en
attente. Ils sont dits dans un état d’interblocage.

4.1. Caractéristiques de l’interblocage


Un ensemble de processus est en état d’interblocage, si chaque processus de l’ensemble est
bloqué en attente d’un évènement qui ne peut être causé que par un autre processus de
l’ensemble.

12
D’une manière plus formelle, on peut identifier quatre conditions nécessaires pour qu’un
interblocage soit possible. Il faut que ces quatre conditions soient vérifiées simultanément pour
qu’un interblocage se produise.

1. Condition d’exclusion mutuelle : Il doit exister au moins deux ressources qui ne sont
pas partageables c.à.d. à un moment donné, chacune est soit attribuée à un processus,
soit disponible.

2. Condition de détention et attente : Il existe un processus qui détient au moins une


ressource et attend de pouvoir acquérir d’autres ressources détenues par d’autre
processus.

3. Condition de non-réquisition : Un processus ne peut pas être forcé par un autre


processus à relâcher les ressources déjà acquises.

4. Condition d’attente circulaire : Il doit exister une chaine circulaire (cycle) d’au moins
deux processus dont chacun attend une ressource détenue par le membre suivant dans
la chaine.

Fig. 4 : Interblocage

4.2. Graphe d’allocation des ressources


On utilise les conventions graphiques suivantes :

- Un processus est représenté par un cercle.

- Une ressource est représentée par une boite rectangulaire. S’il existe plusieurs
instances indifférenciables de la ressource, on les représente dans la même boite. Par le
mot indifférenciable, on veut dire qu’un processus ne devait pas pouvoir choisir une
instance parmi d’autres, il prend la première disponible.

- Un arc qui part d’une instance d’une ressource R et pointe vers un processus P,
signifie que cette instance de R est détenue par P.
13
- Un arc qui part d’un processus P et pointe vers une instance d’une ressource R,
signifie que P est bloqué en attente d’une instance de R.

Exemple : Un processus P1 demande et obtienne la ressource imprimante, seule ressource de


ce type. Un second processus P2 demande ensuite et obtient la ressource dérouleur de bande,
également seule de ce type. Si P1 demande ultérieurement la ressource dérouleur de bande sans
avoir libéré la ressource imprimante, il est bloqué en attendant la libération par P2. Si
maintenant, P2 demande la ressource imprimante avant de libérer la ressource dérouleur de
bande, il est bloqué en attendant la libération par P1 de cette ressource. On voit que chacun des
processus est bloqué, en attendant que l'autre libère la ressource qu'il possède. On dit qu'il y a
un interblocage entre les processus (en anglais deadlock).

Fig. 5 : Allocation conduisant à l'interblocage

On peut constater que dans le graphe final, il y a un cycle représenté par:

En fait, tout interblocage est signalé par la présence d'un tel cycle dans ce graphe, et
inversement, ce qui explique l'intérêt de cette représentation.

4.3. Matrices d’allocation des ressources


Nous considérons un système dans lequel il existe un certain nombre d’instances des différentes
ressources. A tout instant, l’état de chaque processus présent dans le système va être
partiellement décrit par :

- Le nombre d’instances de chaque type de ressources qu’il détient.

14
- Le nombre maximal d’instances de ces ressources dont il a besoin pour
déterminer son exécution et par différence le nombre d’instances des ressources qu’il
doit encore obtenir.

Exemple :

Ressources détenues Maximum Ressources requises


Ra Rb Rc Rd Ra Rb Rc Rd Ra Rb Rc Rd
P1 0 5 1 0 0 10 3 2 P1 0 5 2 2
P2 4 3 5 4 5 5 5 5 P2 1 2 0 1
P3 0 0 0 1 4 5 3 5 P3 4 5 3 4
P4 2 2 2 2 3 2 2 2 P4 1 0 0 0
P5 3 0 1 1 3 1 1 3 P5 0 1 0 2

Pour avoir une image complète de l’état du système vis-à-vis de l’utilisation des ressources, il
nous faut encore préciser le nombre total d’instances de chaque ressource.

Le nombre en total de chaque ressource :

Ra Rb Rc Rd
10 11 9 11

On en déduit le nombre d’instances disponibles pour chaque ressource :

Ra Rb Rc Rd
1 1 0 3

4.4. États d’un système informatique


Nous pouvons distinguer trois types d’états d’un système informatique dans lequel plusieurs
processus partagent plusieurs instances de ressources :

- État d’interblocage : tel que nous l’avons défini précédemment.

- État risqué : appelé aussi état incertain, à partir duquel l’interblocage est
inéluctable (inévitable).

- État sûr : à partir duquel il existe au moins un scenario d’allocation de


ressources permettant d’éviter l’interblocage.

Afin d’éviter qu’un interblocage se produise, il suffit, lorsqu’un processus présente au système
une demande d’allocation des ressources, de vérifier si le nouvel état obtenu serait un état

15
risqué, si c’est le cas, il faut refuser la ressource au processus ‘qui va rester donc bloqué en
attente de cette ressource). Sinon, le processus obtient la ressource demandée.

4.5. Eviter l’interblocage : l’algorithme du banquier


L’algorithme du banquier est une technique permettant d’allouer dynamiquement des
ressources à des processus en assurant qu’aucun processus ne puisse rester infiniment bloqué.

On suppose que nous disposons de plusieurs exemplaires de la même ressource. On construit


un tableau où pour chaque processus on note le nombre maximal de ressources demandables
(Maximum) et le nombre de ressources allouées à un instant donné (ressources détenues).

Par analogie a une banque, le nombre maximal est en quelque sorte le crédit total du processus
et le nombre alloué est l’encours (détenu). La somme totale des crédits peut être supérieure au
nombre des ressources réellement disponibles.

L’algorithme du banquier :

1. Construire la liste L
Répéter
2. Chercher parmi les processus de la liste L s’il y a un processus P dont les besoins
peuvent être satisfaits (nombre de ressources nécessaires<= nombre d’instances de
ressources disponibles)
3. S’il existe, on lui accorde les ressources dont il a besoin puis on le fait terminer. S’il
existe plusieurs choix pour P, on choisit le processus qui va libérer le maximum
d’instances de ressources.
4. Reconstruire le nouvel état du système : Mise à jour de L, des différentes matrices et les
ressources disponibles,
Jusqu’à ce qu’il n’y a plus de processus pouvant terminer.
S’il reste des processus dans L, alors l’état est risqué, sinon, l’état est sûr.

Les matrices d’allocation des ressources apportent donc des informations complémentaires à
celles offertes par les graphes d’allocation : Les graphes nous donnent une image instantanée
sur l’état du processus, mais ne nous permettent pas de prédire l’avenir, tandis que les matrices
d’allocation nous informent sur les futurs besoins de chaque processus et par conséquent les
risques d’interblocage.

Les graphes nous permettent de répondre à la question : l’état courant est-il un état
d’interblocage ?

16
Les matrices nous permettent de répondre aux questions :

- L’état courant est-il un état sûr ou risqué ?

- Peut-on accorder à un processus les ressources qu’il demande ?

4.6. Résolution de l’interblocage


Une fois que l’on a détecté l’existence de l’interblocage et identifier les processus qui y ont
participé, il reste encore à résoudre l’interblocage.

On peut identifier quatre techniques permettant de résoudre l’interblocage :

- Tuer le processus (automatiquement)

- Réquisition des ressources

- Résolution par Rollback (reculer au dernier état sûr)

- Ne rien faire et donner la main à l’utilisateur, pour une correction manuelle.

17