Synchronisation Classique Contexte Le problème de la section critique Matériel pour la synchronisation Sémaphores Problèmes classiques de synchronisation
Contexte Accès concurrent à une mémoire partagée peut produire des incohérences Maintenir la cohérence des données requière des mécanismes pour assurer une exécution correcte des processus coopérants Une solution mémoire partagée au problème de tampon borné (bounded buffer) contient un problème de “race condition” (condition de concurrence) sur la variable donnée count.
Race Condition Le Producteur appelle while (1) { while (count == BUFFER_SIZE) ; // operation nulle // ajouter un element au tampon count++; buffer[in] = item; in = (in + 1) % BUFFER_SIZE; }
Race Condition Le Consommateur appelle while (1) { while (count == 0) ; // operation nulle // retirer un element du tampon count--; item = buffer[out]; out = (out + 1) % BUFFER_SIZE; }
Race Condition count++ peut être implémenté de la façon suivante register1 = count register1 = register1 + 1 count = register1 count– peut être implémenté de la façon suivante register2 = count register2 = register2 - 1 count = register2 Considérez cette exécution: S0: producteur exécute register1 = count {register1 = 5} S1: producteur exécute register1 = register1 + 1 {register1 = 6} S2: consommateur exécute register2 = count {register2 = 5} S3: consommateur exécute register2 = register2 - 1 {register2 = 4} S4: producteur exécute count = register1 {count = 6 } S5: consommateur exécute count = register2 {count = 4}
Solution au Problème de Section Critique 1. Exclusion Mutuelle – Si le processus Pi est dans sa section critique, alors aucun autre processus ne peut exécuter sa section critique 2. Progression – S’il n’y a aucun processus dans la section critique et que des processus veulent entrer dans leur section critique, alors la sélection des processus pour entrer en section critique ne peut pas être retardée indéfininement 3. Attente Bornée - Une borne doit exister sur le nombre de fois que les autres processus sont permis d’entrer dans leur section critique après qu’un processus ait fait une requête d’admission en section critique et avant que cette demande ne soit accordée Supposer que chaque processus exécute à une vitesse différente de 0 Pas d’hypothèses concernant les vitesses d’exécution relatives des processus
Solution pour 2 Tâches 2 Tâches, T0 et T1 (Ti et Tj) 3 solutions présentées. Toutes implémentent l’interface suivante: const int TURN0 = 0; const int TURN1 = 1; void enteringCriticalSection(int turn); void leavingCriticalSection(int turn);
Thread Travailleur while (true) { // initialisation des variables partagées …. // demande d’entrée en section critique enteringCriticalSection(id); // section critique ... // modification des varaibles partagées // sortie de section critique leavingCriticalSection(id); // section NON critique }
Algorithme 1 Les threads partagent un entier turn Si turn==i, thread i rentre dans sa section critique Ne réspecte pas la règle de progession Pourquoi?
Algorithme 1 // Thread 0 turn = TURN0; void enteringCriticalSection(int t) { while (turn != t) laisser_la_main(); } void leavingCriticalSection(int t) { turn = 1 - t;
Algorithme 2 Ajouter plus d’informations sur l’état Drapeau boléen pour indiquer l’intérêt du thread pour entrer dans la section critique Règle de progression toujours pas respectée Pourquoi?
Algorithme 2 void enteringCriticalSection(int t) { { flag0 = true; while(flag1 == true) laisser_la_main(); } void leavingCriticalSection(int t) flag0 = false;
Algorithme 3 Combine les 2 idées précédentes Règles respectées?
Algorithme 3 // Thread 0 void leavingCriticalSection(int t) void enteringCriticalSection(int t) { int other = 1 - t; turn = other; if (t == 0) { flag0 = true; while(flag1 == true && turn == other) laisser_la_main(); } void leavingCriticalSection(int t) { flag0 = false;
Matériel de Synchronisation Plusieurs systèmes fournissent un support matériel pour gérer des sections critiques Uniprocesseurs – bloquer les interruptions Le code en exécution s’exécute sans préemption Générallement pas efficace sur des multiprocesseurs OSs utilisant cette technique ne passent pas à l’échelle Machines modernes fournissent des instructions spécifiques Atomiques = non-préemptables Soit tester un mot mémoire et le modifier Ou échanger le contenu de deux mots mémoire
Structures de Données pour Solutions Matérielles typedef struct { boolean data; } HardwareData; boolean get(HardwareData *var) { return var->data; } void set(HardwareData *var, boolean data) { var->data = data;
Structure de Données pour Solutions Matérielles - cont boolean TestAndSet(HardwareData *var) { boolean oldValue = get(var); set(var, true); return oldValue; } void swap(HardwareData *this, HardwareData *other) { boolean temp = get(this); set(this, get(other)); set(other, temp);
Thread Utilisant TestAndSet Verrou // Verrou partagé par tous les threads HardwareData verrou = { false }; while (true) { while (TestAndSet(verrou)) laisser_la_main(); sectionCritique(); set(verrou, false); sectionNonCritique(); }
Thread Utilisant Instruction swap // verrou partagé par tous les threads HardwareData verrou = {false}; // chaque thread a une copie locale de clef HardwareData clef = {true}; while (true) { set(clef, true); do { swap(verrou, clef); } while (get(clef) == true); sectionCritique(); set(verrou, false); sectionNonCritique(); }
Sémaphore Outil de synchronisation ne provoquant pas du “busy waiting” (attente active) (spin lock) Semaphore S – entier Deux opérations standards modifient S: acquire() and release() A l’origine (Hollandaise) appelées P() and V() Moins compliqué Peut être accédé via deux opérations indivisibles (atomiques) acquire(S) { while S <= 0 ; // no-op S--; } release(S) { S++;
Sémaphore comme Outil de Synchronisation Général Sémaphore de Comptage – valeur entière sur un domaine non restreint Sémaphore Binaire – valeur entière 0 ou 1; plus simple à implémenter Connu aussi sous le nom de as verrou d’exclusion Peut implémenter un sémaphore de comptage S avec des sémaphores binaires Fournit l’exclusion mutuelle
Synchronisation avec Sémaphores Semaphore sem; // initialisé à 1 while (true) { acquire(sem); // section critique release(sem); // section non critique }
Implémentation Sémaphores acquire(S){ valeur--; if (valeur < 0) { ajouter processus à liste bloquer; } release(S){ valeur++; if (valeur <= 0) { supprimer un processus P de liste réveiller P;
Implémentation Sémaphores Doit garantir que deux processus ne puissent pas exécuter acquire() et release() simultanément sur un même sémaphore Ainsi l’implémentation devient le problème de la section critique Peut maintenant avoir de l’attente active dans l’implémentation de la section critique Mais le code de l’implémentation est court Très peu d’attente active si la section critique est très peu occupée Les applications peuvent rester longtemps dans une section critique Problèmes de performance discutés dans cette conférence
Deadlock et Famine Deadlock – deux ou plusieurs processus en attente infinie pour un évènement qui ne peut être provoqué que par un processus en attente Soient S et Q deux sémaphores initialisés à 1 P0 P1 acquire(S); acquire(Q); acquire(Q); acquire(S); . . release(S); release(Q); release(Q); release(S); Famine – blocage infini. Un processus peut ne jamais être repêché de liste des processus bloqués du sémaphore.
Problèmes Classiques de Synchronisation Problème du Tampon Borné (Bounded Buffer) Problème des Lecteurs/Ecrivains Problème des Philosophes
Problème du Tampon Borné const int BUFFER_SIZE = 5; Objet *buffer[BUFFER_SIZE]; int in, out; // initialisé à 0 Semaphore mutex; // initalisé à 1 Semaphore empty; // initialisé à BUFFER_SIZE Semaphore full; // initialisé à 0
Problème du Tampon Borné:Méthode insert() void insert(Objet *item) { acquire(empty); acquire(mutex); // ajouter un item au tampon buffer[in] = item; in = (in + 1) % BUFFER_SIZE; release(mutex); release(full); }
Problème du Tampon Borné:remove() Objet *remove() { acquire(full); acquire(mutex); // supprimer un item du tampon Objet *item = buffer[out]; out = (out + 1) % BUFFER SIZE; release(mutex); release(empty); return item; }
Problème du Tampon Borné:Producteur Objet *message; while (true) { // des instructions diverses … // produire l’item & insérer dans le tampon message = produireMessage(); insert(message); }
Problème du Tampon Borné:Consommateur Objet *message; while (true) { // instructions diverses … // consommer un item du tampon message = remove(); }
Problème Lecteurs-Ecrivains: Lecteur RWLock db; while (true) { // instructions diverses ... acquireReadLock(db); // on a maintenant un accès lecture à la base de données // lire de la base de données // relâcher le verrou releaseReadLock(db); }
Problème Lecteurs-Ecrivains: Ecrivain RWLock db; while (true) { acquireWriteLock(db); // on a un accès en écriture // écrire releaseWriteLock(db); }
Lecteurs-Ecrivains: Base de Données typedef struct { int readerCount; // initialisé à 0 Semaphore mutex; // initialisé à 1 Semaphore db; // initialisé à 1 } RWLock;
Lecteurs-Ecrivains: Méthodes Lecteur void acquireReadLock(RWLock verrou) { acquire(&verrou.mutex); ++readerCount; // si je suis le 1er à lire, dire aux autres // que la base de données est en train d’être lue if (readerCount == 1) acquire(&verrou.db); release(&verrou.mutex); } void releaseReadLock(RWLock verrou) { --readerCount; // si je suis le dernier à lire, dire aux autres // que la base de données n’est plus lue if (readerCount == 0) release(&verrou.db);
Lecteurs-Ecrivains: Méthodes Ecrivain void acquireWriteLock() { acquire(&verrou.db); } void releaseWriteLock() { release(&verrou.db);
Problème des Philosophes Données partagées Semaphore *baguettes[5];
Problème des Philosophes (Cont.) Philosophe i: while (true) { // prendre baguette de gauche acquire(baguette[i]); // prendre baguette de droite acquire(baguette[(i+1)%5]); manger(); // rendre baguette de gauche release(baguette[i]); // rendre baguette de droite release(baguette[(i+1)%5]); penser(); }
Problème des Philosophes (Cont.) Début P(mutex) Si les deux voisins immédiats ne mangent pas Alors V(sémaphore privé) Etat = mange Sinon Etat = veut manger FSi V(mutex) P(sémaphore privé) mange ... Fin