Académique Documents
Professionnel Documents
Culture Documents
Outils de Synchronisation
Plusieurs outils de synchronisation ont été proposé : les verrous, les événements, les sémaphores,
les moniteurs, les régions critiques et d’autres.
On peut voir un verrou comme un objet ayant deux attributs: une variable booleaine state et une file
d'attente q. state peut être false(ouvert) ou true (fermer),
La file d'attente q contient les processus en attente devant un verrou fermé.
struct Lock
{ boulean state=false; // etat du verrou, false:ouvert, true: fermer
//initialisé à false.
queue q; // file d’attente
}
lock(Lock *V)
{
if (V.state == false) {V.state = true;}
else {bloquerleprocessusetlajouteralafile V.q}
}
unlock(Lock *V)
{
if (!V.q vide){débloquéunseulprocessusdelafile V.q}
else {V.state = false;}
}
Lock v;
process p1;
{
lock(v);
/*SECTION CRITIQUE*/
unlock(v);
}
2.3.1.2 Les événements
L’événement est l’outil le plus primitif permettant de résoudre le problème de synchronisation. Il
permet à un processus d'attendre la survenu de n'importe quelle evenement comme l'arrivé d'un
message par exemple.
Un événement peut avoir eu lieu ou ne pas avoir eu lieu (initialement l’événement n’a pas eu lieu).
Il est doté d'une liste d’attente contenant les éventuels processus en attente de l’événement. Un
évènement peut être manipulé grâce aux procédures wait, set, et clear définies comme suit :
wait(e):lorsqu'il est exécuté, provoque la suspension du processus en cours d'exécution jusqu'à ce
que l'état de l'événement e.happen soit vrai. Si l'état est déjà vrai (e.happen=true) avant l'appel de
wait, wait n'a aucun effet.
set(e)définit l'état de l'événement à vrai, libère tous les processus en attente.
clear(e) définit l'état de l'événement à faux.
struct event
{ boulean happen=false; // etat de l'evenement: true:survenu, false:non survenu
//initialisê à false
queue q; // file d’attente
}
wait(event *e)
{
if (!e.happen){bloquerleprocessusetlajouteralafile e.q}
}
set(event *e)
{
e.happen=true;
débloquertouslesprocessusdanslafile e.q;
}
clear(event *e)
{
e.happen=false;
}
L'exemple suivant permet de synchroniser deux processus, le premier (sender) envoi un message au
second (receiver), ce dernier doit attendre la reception du message.
L’opération P(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.
L’opération V(S) incrémente la valeur du sémaphore S, si aucun processus n’est bloqué par
l’opération P(S). Sinon, l’un d’entre-eux sera choisi et redeviendra prêt.
Chacune de ces deux opérations doit être implémentée comme une opération indivisible.
struct Semaphore
{ int count; // valeur du sémaphore
queue q; // file d’attente
}
P(Semaphore *S)
{
S.count= 1;
if (S.count < 0) {bloquerleprocessusetlajouteralafile V.q}
}
V(Semaphore *S)
{
S.count++ ;
if (S.count <=0){débloquéunseulprocessusdelafile V.q}
}
NOTES :
1- Le système garanti que les deux fonctions P() et V() sont exécutées en exclusion mutuelle en
utilisant le masquage des interruptions en monoprocesseur. En multiprocesseur, la solution est
l'utilisation de l’instruction Test and Set Lock.
2- Le sémaphore doit toujours être initialisé lors de sa création.
3- L'attribut S.count d'un Sémaphore ne peut être accédé pendant l’exécution.
4- Nous donnons des noms aux sémaphores selon leur utilisation :
- Sémaphore privé : S.count initialisé à 0 ;
- Sémaphore d’exclusion mutuelle : S.count initialisé à 1;
- Sémaphore général : S.count initialisé à une valeur N>0 ;
5- Si S.count <0, alors le nombre de processus bloqué dans la file S.q est égale à |S.count|
semaphore mutex = 1 ;
Processus P1 : Processus P2 :
{ { P(mutex)
P(mutex) Section_critique_de_P2();
Section_critique _de_P1() ; V(mutex) ;
V(mutex) ; }
}
2.3.2.3 Intérêt et inconvénient des sémaphores :
le sémaphore est mécanisme simple meilleur que l'attente active et peu coûteux. Généralement les
sémaphores sont implanté au niveau noyau système. Leur utilisation est un peut difficile, Cela est
du a la possibilité de tomber facilement dans un problème appelé interblocage définie comme suit :
soit deux processus P et Q désirant entrer en même temps dans deux sections critiques imbriquées.
semaphore mutax1, mutex2 ; le processus Q execute :
.
Si une interruption horloge se produit juste après que le processus P exécute l'instruction
P(mutex1), le processus Q exécute P(mutex2) et se bloque dans P(mutex1). Le processus P se
bloque aussi dans P(mutex2). Les deux processus seront bloqués infiniment. On appel cette
situation : interblocage. Ce problème sera discuter dans le dernier chapitre de ce cours.
Shared Struct data
{
/* declaration des variables partagées*/
}
Cela signifie que la variable peut être utilisée par plusieurs processus (mais pas en même temps).
L’accès aux variables partagées data doit toujours être dans un bloque ayant la structure syntaxique
suivante :
region Data do{/*section critique utilisant les variables
déclarées dans data*/};
region est un mot clé. data définit les données communes utilisées par la section critique.
L'instruction region permet donc une programmation plus structurée. La section critique doit être
définie comme un bloc d'instruction à l'intérieur de l'instruction region. Une erreur de l'usager sera
alors détectée par le compilateur. L'instruction region assure qu'un seul processus exécute la section
critique à la fois (c’est une garantie pour le programmeur).
Pour bloquer des processus en attente d'une condition, Hoare a proposer l'instruction suivante :
Region data when B do S;
data est une variables déclarée shared, B est une expression booléenne et S une section de code
critique.Cela signifie : quand un processus essaye de rentrer dans la région, l’expression boolienne
B est évaluer, si l’expression est vrai alors l’instruction S est exécuter. Si elle est fausse, le
processus abandonne l’exclusion mutuelle et il est bloqué jusqu’à ce que B devienne vraie et
qu’aucun autre processus ne se trouve dans la zone associée à data. Chaque fois qu’un processus
quitte une région critique, tous les processus bloqués en attente de la condition B doivent se
réveiller pour réévaluer la condition B. ce qui est très coûteux sur le plan performances.
L’expression B est réévaluée par tout les processus en attente à chaque fois qu’un processus
abandonne la région critique.
A noté aussi que les régions critiques sont implantées au niveau bibliothèque. Le compilateur traduit
tout bloque de région critique par des sémaphores en insérant des P() et des V() sur les sémaphores
de tel sorte que la condition B soit toujours respecter et les variables déclaré dans data seront
accédés en exclusion mutuelle.
Le mécanisme des moniteurs est un outil évolué de synchronisation proposé par Hoare &
Brinch.Hansen 1974, il fournit un mode d’expression de la synchronisation plus structuré que les
sémaphores, en vue de faciliter la compréhension et l’écriture des schémas de synchronisation, de
donner à ces schémas une forme synthétique.
Un moniteur est un module de programme,comportant des variables d’état internes et des
procédures externes (ou points d’entrée) accessibles depuis l’extérieur. Un moniteur est une
structure partagée entre plusieurs processus, qui se synchronisent en appelant ses points d’entrée
(les procédures) ;les variables d’état sont privés et ne sont pas directement accessibles. Les
procédures du moniteur contiennent des opérations qui permettent de bloquer ou de réveiller des
processus qui utilisent le moniteur. Une procédure d’initialisation (constructeur) est exécutée une
seule fois lors de la création du moniteur. Les constructions de synchronisation s’expriment à l’aide
d'un nouvau type appelée conditions. Une condition c est déclarée comme une variable, mais ne
peut être manipulée qu’au moyen trois primitives
Soit p désigne le processus appelant :
Les variables d’état du moniteur doivent être manipulées en exclusion mutuelle pour garantir leur
cohérence. Lorsqu’un processus P réveille un processus Q par l’exécution de signal, le processus p
doit donc se bloquer momentanément pour assurer cette exclusion mutuelle, jusqu’au blocage ou à
la sortie de Q. Un processus qui est ainsi temporairement bloqué après avoir exécuté signal
bénéficie d’une priorité pour la reprise de son exécution, en vue d’éviter un blocage indéfini.
File d'attente
de la
File d'attente
condition c1
de la
condition c1
}
File d'attente
du moniteur
public g(){
.
.signal(c1)
.
}
M(){
/*constructeur*/
}
Remarque : Un moniteur est en général implémenté avec des sémaphores, c'est tout à fait logique,
un système est bâti par niveaux d'abstraction successifs, un niveau i étant implémenté par les
primitives du niveau i-1.
monitor Compte {
int solde :; //variable manipulable seulement par des procédures du moniteur
public void Ajouter(x) ;
{
solde +=x ;
}
public void retirer(x) ;
{
solde =x ;
}
Compte() // procédure d’initialisation
{
solde=0 ;
}
}