4. Outils pour la synchronisation F. Boyer, Laboratoire Sardes Fabienne.Boyer@imag.fr Le problème Insuffisance des solutions de base (verrous) Les solutions de plus haut niveau Les Sémaphores Les Moniteurs Les Sections Critiques Conditionnelles
Ce cours a été conçu à partir de Cours de E. Berthelot http://www.iie.cnam.fr/%~eberthelot/ Cours de A. Sylberschatz www.sciences.univnantes.fr/info/perso/permanents/attiogbe/systeme/courssysteme.html Cours de A. Griffaut http://dept-info.labri.fr/~griffault/enseignement/se/cours Cours de H. Bouzourfi, D. Donsez http://www-adele.imag.fr/~donsez/cours/#se
Insuffisance des solutions de base pour la synchronisation Les verrous représentent une solution simple Ils ne permettent pas à eux-seuls de gérer des attentes passives conditionnelles
Exemple : parking Parking de N places gestion des entrées et des sorties Code non synchronisé (incorrect): données partagées: int n =..; // number of places void enter ( ) { void leave( ) { while (n==0) ; n++; n--;
Exemple : parking (2) Code synchronisé (incorrect) : données partagées: int n =..; // number of places Lock l; // synchronizing any access to n void enter ( ) { void leave( ) { l.enter(); l.enter(); while (n==0) ; n++; n--; l.exit(); l.exit();
Exemple : parking (3) Code synchronisé : données partagées: int n =..; // number of places Lock l; // synchronizing any access to n void enter ( ) { void leave( ) { l.enter(); l.enter(); while (n==0) { n++; l.exit(); sleep(..); l.enter(); l.exit(); n--; l.exit(); Attente «semi-active»
Exemple : parking (4) Code synchronisé: données partagées: int n =..; // number of places void enter ( ) { void leave( ) { if (n==0) <wait>; n++; n--; if (<a processus is waiting>) <wakeup the processus>; Sections critiques
Solutions de plus haut niveau Les principes : exploiter la sémantique de l application pour Endormir un processus lorsqu il ne peut pas continuer à s exécuter Le réveiller lorsqu il peut continuer à s exécuter Les solutions : Les sémaphores Attente passive conditionnelle Les moniteurs Les sections critiques conditionnelles
Sémaphores (Dijkstra, 1965) Sémaphore S : compteur S.c file d attente S.f Modélise un nombre de ressources (ou une condition de passage) Processus en attente Fournit 2 opérations atomiques : P V Prendre une ressource Libérer une ressource
Sémaphores (Dijkstra, 1965) stop() suspend le processus courant wakeup(p) remet P dans la file des prêts wait() ou P (): S.c-- if S.c < 0 do { // no more free resources put(myself, S.f); stop(); // suspension signal() ou V (): S.c++; if (S.c <= 0) do { // at least 1 waiting process get(p, S.f); wakeup(p); Sections critiques
Sémaphores Compteur S.c == S.c initial + NV - NP NV est le nombre d opérations V exécutées sur le sémaphore NP est le nombre d opérations P exécutées sur le même sémaphore Compteur S.c < 0 : correspond au nombre de processus bloqués Compteur S.c > 0 : correspond au nombre de ressources disponibles Compteur S.c == 0 : aucune ressource disponible et aucun processus bloqué
Mise en oeuvre des SC avec un sémaphore Données partagées: Semaphore mutex = new Semaphore(1); // initial value for the Semaphore s counter Processus Pi: mutex.p(); <critical section> mutex.v();
Application bancaire avec un sémaphore Les opérations suivantes doivent être exécutées en exclusion mutuelle : cpte = cpte + montant; cpte = cpte - montant; Ces deux opérations sont des sections critiques
Application bancaire avec un sémaphore semaphore mutex = new Semaphore(1); // initial value for the Semaphore s counter credit(account, amount) : mutex.p(); account= account+ amount; mutex.v(); PB : séquentialisation des opérations qqsoit le compte Utiliser un sémaphore par compte?
RDV avec un sémaphore Exécuter B dans P2 seulement après que A ait été exécuté dans P1 Utilise un sémaphore rdv initialisé à 0 P1 M A rdv.v(); P2 M rdv.p(); B Le sémaphore exprime les conditions d attente et de réveil
Parking avec un sémaphore Problème du parking N places Entrée() et Sortie() Semaphore s = new Semaphore(N); enter ( ) leave( ) s.p(); s.v();
Problème du producteur/consommateur Msg Producteur Consommateur Conditions de dépôt et de retrait Dépôt : buffer non plein Retrait : buffer non vide
Producteur / Consommateur Méthodes de conception : Soit on utilise des variables pour évaluer les conditions de dépôt et de retrait, et on protège ces variables par un sémaphore mutex Boolean notempty, notfull; Ou bien : Int nbempty, nbfull; Soit on utilise des sémaphores pour représenter les conditions de dépôt et de retrait Semaphore notempty, notfull;
Producteur / Consommateur Données partagées : Msg buffer[] = new Msg[1]; // production condition Semaphore notfull = new Semaphore(1); // consommation condition Semaphore notempty = new Semaphore(0); Semaphore mutex = new Semaphore(1);
Producteur / Consommateur Processus Producteur : produce (Msg msg) { // if the buffer is full, wait until it becomes empty notfull.p(); mutex.p(); buffer[0] = msg; mutex.v(); // wakeup some waiting process notempty.v();
Producteur / Consommateur Processus Consommateur Msg Consume { // if buffer is empty, wait until it becomes full notempty.p(); mutex.p(); Msg msg = buffer[0]; mutex.v(); // wakeup some waiting process notfull.v(); return msg; Mutex nécessaire?
Producteur / Consommateur Gestion d un buffer de N cases (N>1) Données partagées : int buffersz; Msg buffer[]; Semaphore notfull; Semaphore notempty; int in = 0, out = 0; Semaphore mutex; Initialisation : public ProdCons(int buffersz) { this.buffersz = buffersz; buffer = new Msg[bufferSz]; notfull = new Semaphore(bufferS notempty = new Semaphore(0); mutex = new Semaphore(1);
Producteur / Consommateur Processus Producteur, buffer à N cases (N>1) : Produce(Msg msg) { // if the buffer is full, wait until it becomes empty notfull.p(); mutex.p(); buffer[in] = msg; in = in + 1 % buffersz; mutex.v(); // wakeup some waiting process notempty.v();
Producteur / Consommateur Processus Consommateur,buffer à N cases (N>1) Msg Consume() { // if buffer is empty, wait until it contains one message notempty.p(); mutex.p(); Msg msg = buffer[out]; out = out + 1 % buffersz; mutex.v(); // wakeup some waiting process notfull.v();
Producteur / Consommateur Pas de parallélisme entre productions et consommations -> manque d efficacité Les opérations suivantes doivent être exclusives in = in + 1 % buffersz (resp.) out = out + 1 % buffersz mettre en SC la manipulation de in et de out Sémaphore mutexin = new Semaphore(1); Sémaphore mutexout = new Semaphore(1);
Producteur / Consommateur Processus Producteur, buffer à N cases (N>1) Produce(Msg msg) { // if buffer is full, wait for one empty entry notfull.p(); mutexin.p(); buffer[in] = msg; in = in + 1 % buffersz; mutexin.v(); // wakeup some waiting process notempty.v();
Producteur / Consommateur Processus Consommateur, buffer à N cases (N>1) Msg Consume() { notempty.p(); // if buffer is empty, wait for one item mutexout.p(); Msg msg = buffer[out]; out = out + 1 % buffersz; mutexout.v(); notfull.v(); return msg;
Interblocages Les sémaphores ne garantissent pas une solution correcte : P(mutex); if P(S); interblocage possible else V(S); V(mutex); REGLE: jamais de blocage dans une SC sans libérer la SC La gestion des interblocages est étudiée au chapître 4
Le moniteur Module comprenant Des données Des procédures d accès (P1,..,Pn) Une procédure d initialisation Des conditions Les procédures sont exécutées en exclusion mutuelle Une condition est une structure fournissant deux opérations : - wait() bloque le processus courant - signal() réveille un processus bloqué s il y en a un. Le signal est fugace. en général, les conditions sont gérées de manière FIFO
Schéma de moniteur monitor <monitor-name> { <shared variables + conditions declarations> procedure P1 ( ) {... procedure P2 ( ) {... procedure Pn ( ) {... { initialization code
Vue schématique d un moniteur
Fonctionnement d un moniteur Au maximum un seul processus actif dans le moniteur Lors d un signal Soit le signalant garde le moniteur (priorité signalant) Soit le signalé prend le moniteur (priorité signalé) Libération du moniteur Lorsque la procédure en cours est terminée Lors d un wait Lors de la libération du moniteur Le moniteur peut être alloué en priorité à un processus déjà dans le moniteur (un signalé ou un signalant selon la priorité appliquée lors du signal) Ou bien il n y a pas de règle (tous les processus sont en concurrence)
Moniteur SectionCritique Monitor SC { boolean free = true; Condition sc; procedure entrysc() while (! free) sc.wait(); free = false procedure exitsc() free = true sc.signal(); // appel systématique
Moniteur Producteur/Consommateur Monitor ProdConsMonitor int buffersz = 0; int nbmsg = 0; Msg buffer[]; Condition notempty, notfull; procedure ini(int buffersz) this.buffersz = buffersz; buffer = new Msg[buffersz]; procedure produce(msg msg) if (nbmsg==buffer_sz) notfull.wait(); buffer[in] = msg; in = in + 1 % BUFFER_SZ; nbmsg++; procedure consume() : Msg if (nbmsg==0) notempty.wait(); Msg msg = buffer[out]; out = out + 1 % BUFFER_SZ; nbmsg-- notfull.signal(); notempty.signal();
Les sections critiques conditionnelles Outil de synchronisation POSIX (verrou + condition) Gestion explicite de l exclusion mutuelle lock(verrou) wait(verrou, condition) signal(verrou, condition) unlock(verrou) wait( V, C ) libère le verrou V, bloque le processus courant sur C, puis reprend le verrou V à son réveil signal( V, C ) réveille un processus bloqué sur C, s il y en a un. (fugace) En général, verrous FIFO
SCC Parking SCC Parking { Condition freeplaces; Lock v; int nfree; void init (int nbplaces) { nfree = nbplaces; void enter() lock(v); (if nfree == 0) wait (v,freeplaces); nfree--; unlock(v); void exit () lock(v); n--; signal(v,freeplaces); unlock(v);
SCC «Moniteur» (Implantation procédurale d un moniteur avec une SCC) monitor M { condition c; procedure P ( ) { wait(c)... signal(c) SCC M { condition c; lock l; void P ( ) { lock(l);... wait(c, l);... signal(c,l)... unlock(l);
Implantation des SCC priorité signalant avec vol de cycles class Scc { Lock mutex, condition; int nbwaiting; // number of processes waiting for the Scc public Scc { mutex = new Lock(); // this lock manages the Scc mutual exclusion condition = new Lock(); // this lock manages a FIFO waiting set of threads condition.lock(); nbwaiting = 0; public void lock () { mutex.lock(); public void unlock () { mutex.unlock(); // ensure that following calls to condition.lock will block
Implantation des SCC (2) priorité signalant avec vol de cycles public void wait() { nbwaiting++; mutex.unlock(); // free the Scc condition.lock(); // block the current process mutex.lock(); // come back in the Scc public void signal() { if (nbwaiting > 0) { nbwaiting--; condition.unlock(); // wake up a waiting process public void signalall() { while (nbwaiting > 0) { nbwaiting--; condition.unlock();
Implantation des SCC à base de verrous priorité signalant sans vol de cycles class Scc { Lock mutex; Lock condition; int nbwaiting; // number of processes waiting for the Scc Lock wakeup; // to manage the coming back of the wakeup processes in the Scc int nbwakeup; // number of wakeup processes public Scc { mutex = new Lock(); condition = new Lock(); waked = new Lock(); condition.lock(); wakeup.lock(); nbwaiting = 0; nbwakeup = 0; // ensure that following calls to condition.lock will block // ensure that following calls to wakeup.lock will block
Implantation des SCC (2) priorité signalant sans vol de cycles public void lock () { mutex.lock(); public void unlock () { if (nbwakeup > 0) { nbwakeup --; wakeup.unlock(); // give the Scc to a wakeup process else { mutex.unlock(); // free the Scc
Implantation des SCC (3) priorité signalant sans vol de cycles public void wait() { nbwaiting++; mutex.unlock(); condition.lock(); // block myself in the condition waiting-set wakeup.lock(); //block myself in the wakeup waiting-set public void signal() { if (nbwaiting > 0) { nbwaiting--; condition.unlock(); nbwakeup++; public void signalall() { while (nbwaiting)> 0) { nbwaiting--; condition.unlock(); nbwakeup++;
Synchronisation Java Eléments synchronisés : Objets Classes Principes d un moniteur Méthodes synchronisées = exécutées en exclusion mutuelle Opérations wait et notify/notifyall pour gérer des conditions class Example { int cpt; // shared data public void synchronized get() { if (cpt <= 0) wait(); cpt--; public void synchronized put() { cpt++; notify();
Principes d implantation de la synchronisation Java (1) Tout objet possède un verrou (transparent / programmeur) Toute classe possède un verrou (transparent / programmeur) Gestion FIFO / non FIFO dépend du JDK Méthodes synchronisées : Méthode d instance : currentobject.lock() <méthode> currentobject.unlock() Méthode de classe (static): currentclass.lock() <méthode> currentclass.unlock()
Principes d implantation de la synchronisation Java (2) Tout objet / classe synchronisé(e) possède un verrou et une file de processus bloqués wait() lock() put(current_thread, blocked) current.lock() current.unlock() <stop the current thread> current.lock() notify() unlock() if!empty(blocked) { current.unlock(); thread = get(blocked) wakeup(thread)
Codage des sémaphores en Java (<1.5) Class Semaphore { private int count; public semaphore(int count) { this.count = count; public synchronized void P() throws Exception { if ((--count) < 0) wait(); public synchronized void V() throws Exception { if ((++count) <= 0) notify();
Classe Semaphore prédéfinie par Java (1.5) class Semaphore { public void acquire() // Acquires a permit from this semaphore, blocking until one is available, or the thread is interrupted. public int availablepermits() // Returns the current number of permits available in this semaphore. public Collection<Thread> getqueuedthreads() // Returns a collection containing threads that may be waiting to acquire. public boolean isfair() // Returns true if this semaphore has fairness set true.protected public void release() // Releases a permit, returning it to the semaphore. public boolean tryacquire() // Acquires a permit from this semaphore, only if one is available at the time of invocation.
Interface Lock prédéfinie par Java (1.5) interface Lock { public void lock() // Acquires the lock. public boolean trylock() // Acquires the lock only if it is free at the time of invocation. public void unlock // Releases the lock. public Condition newcondition // Returns a new condition instance that is bound to this lock instance. Implemented by : ReentrantLock, ReentrantReadWriteLock,
Interface Condition prédéfinie par Java (1.5) interface Condition { public void await(); // Causes the current thread to wait until it is signalled or interrupted. public void signal(); // Wakes up one waiting thread. public void signalall(); // Wakes up all waiting threads.
Classe BlockingQueue prédéfinie par Java (1.5) interface BlockingQueue { A Queue that additionally supports operations (as put / take) that wait for the queue to become non-empty when retrieving an element, and wait for space to become available in the queue when storing an element Direct Known Subclasses: ArrayBlockingQueue ( a queue implemented with an array) DelayQueue (an element can be taken when its delay has expired) LinkedBlockingQueue (elements are ordered / FIFO) PriorityBlockingQueue (elements are ordered according to their priority) SynchronousQueue ( a put wait for a take and reversely)
Outils de synchronisation avec Windows 2000 Utilise le masquage des interruptions pour gérer l exécution en exclusion mutuelle sur les systèmes mono-processeurs. Utilise les spinlocks sur les systèmes multiprocesseurs. Fournit des constructions de plus haut niveau (dispatcher objects ) qui peuvent être utilisés comme des sémaphores. Les dispatcher objects fournissent la notion d événement, qui peut être utilisé comme condition.
Problèmes classiques de synchronisation Allocation de ressources (parking, sac de jetons, ) Producteurs / Consommateurs Lecteurs / Rédacteurs Philosophes mangeurs de spaguettis étudiés en TD