Process Synchronization
Gestion des accès concurrents aux ressources partagées — Section critique, Sémaphores, Moniteurs, Problèmes classiques
Résultat dépend de l'ordre d'exécution des opérations. Le résultat est non déterministe.
Ressource dont les accès concurrents peuvent mener à un état incohérent (ex: variable partagée).
Portion de code manipulant une ressource critique. Un seul processus à la fois doit l'exécuter.
Garantit qu'un seul processus est dans sa SC à un instant donné — mécanisme d'atomicité.
/* Structure typique d'un processus Pi */ do { entry_section() ; // Demande d'accès à la SC /* ═══ SECTION CRITIQUE ═══ */ // Modifier variables communes, écrire fichier... exit_section() ; // Libération de la SC /* Remainder section */ } while (true) ;
| Solution | Type | Limite |
|---|---|---|
| Peterson / Dekker | Logicielle | 2 processus seulement, pas garanti sur toutes architectures |
| Masquage interruptions | Matérielle | Dangereux en multiprocesseur, perturbe l'horloge |
| Test & Set / Swap | Matérielle | Difficile en multiprocesseur, complexe à utiliser |
| Verrou (Mutex) | OS | Attente active → gaspille CPU |
| Sémaphore | OS ✓ | Solution complète, sans attente active |
| Moniteur | OS ✓✓ | Niveau supérieur, géré par compilateur (Java, C#) |
Un sémaphore S est constitué de : un compteur entier + une file d'attente F de processus bloqués.
WAIT / P / down(S)
wait(S): S.value = S.value - 1 if S.value < 0: // ajouter process à S.L block() // mise en sommeil
→ Demande d'autorisation. Si S ≤ 0, le processus est bloqué.
SIGNAL / V / up(S)
signal(S): S.value = S.value + 1 if S.value <= 0: // retirer process P de S.L wakeup(P) // réveil
→ Libération. Si des processus attendent, un est réveillé.
Valeur ∈ {0,1}. Init à 1 → exclusion mutuelle. Init à 0 → bloquant. Contrôle accès à une ressource unique.
Valeur peut être > 1. Init = nombre de ressources disponibles. Utile pour plusieurs instances identiques.
/* n processus partagent un sémaphore mutex initialisé à 1 */ Semaphore mutex = 1; repeat wait(mutex); // Entrée SC /* section critique */ signal(mutex); // Sortie SC /* remainder section */ until false;
// Déclaration sem_t mutex; // Initialisation : pshared=0 (thread local), value=valeur init sem_init(&mutex, 0, 1); // Attente (décrémente) — BLOQUANT sem_wait(&mutex); // Attente (décrémente) — NON BLOQUANT (retourne -1 si occupé) sem_trywait(&mutex); // Libération (incrémente + réveille un processus si file non vide) sem_post(&mutex); // Lire valeur courante sem_getvalue(&mutex, &sval); // Destruction sem_destroy(&mutex);
prêt
prêt
prêt
Un producteur génère des données placées dans un tampon de N cases, qu'un consommateur retire. Gestion : Overflow Underflow
/* Producteur */ /* Consommateur */ repeat repeat produire nextp wait(full) wait(empty) // case disponible? wait(mutex) wait(mutex) // accès exclusif retirer item ajouter nextp au buffer signal(mutex) signal(mutex) signal(empty) // libère case signal(full) // case remplie consommer item until false; until false;
Accès concurrent à une base de données. Plusieurs lecteurs simultanément OK. Un rédacteur → accès exclusif total.
- mutex = 1 : protège la variable NbL (compteur de lecteurs)
- redact = 1 : exclusion mutuelle pour les rédacteurs
- NbL = 0 : nombre de lecteurs actifs
- 1er lecteur →
wait(redact); Dernier lecteur →signal(redact)
Problème 2 (priorité rédacteurs) : famine possible pour les lecteurs.
5 philosophes autour d'une table, 5 fourchettes. Pour manger : besoin des 2 fourchettes adjacentes.
Solutions : limiter à 4 philosophes à table, prendre 2 fourchettes atomiquement, asymétrie (philosophe pair/impair). C'est aussi un modèle du problème d'inter-blocage.
Encapsule variables partagées + sections critiques d'un même problème. Le compilateur gère l'exclusion mutuelle automatiquement.
Un seul thread actif dans le moniteur à tout moment. Le compilateur insère le code de verrouillage (Java: synchronized).
class CompteBancaire { private int solde = 0; // synchronized → une seule méthode active à la fois public synchronized void deposer(int montant) { solde = solde + montant; } public synchronized void retirer(int montant) { if (solde >= montant) solde = solde - montant; } }
wait()) en libérant le verrou, et d'être réveillés par un autre thread (notify()).🧪 QCM — Synchronisation
IPC — Communication Interprocessus
Mécanismes permettant aux processus de communiquer et synchroniser leurs actions : mémoire partagée, tubes, signaux, sockets, messages.
Independent Process
- N'affecte pas les autres processus
- Ne partage aucune donnée
- Pas besoin de synchronisation
Cooperating Process
- Affecte ou est affecté par les autres
- Partage des données (fichier, mémoire)
- Nécessite IPC pour communiquer
Partage d'info (fichier commun) · Speedup (parallélisme) · Modularité · Commodité (multitâche)
📦 Message Passing
- send(message) / receive(message)
- Un seul processus a les données à la fois
- Communication directe : send(P, msg) / receive(Q, msg)
- Communication indirecte : via boîte aux lettres (port)
- Utile pour petites quantités de données
- Pas de problème de conflit (pas de vars partagées)
💾 Shared Memory
- Région de mémoire commune entre processus
- Accès en lecture/écriture non exclusifs par défaut
- Plus rapide (pas d'appels système pendant l'accès)
- Nécessite synchronisation ! (sémaphores)
- Exemple POSIX :
shm_open()
- Unidirectionnel : un côté lecture, un côté écriture
- Réservé aux processus ayant un ancêtre commun (après fork())
- Pas de nom dans le système de fichiers → anonyme
- Stocké dans le kernel, capacité fixe (PIPE_BUF)
- Opérations : read(), write(), close() — comme un fichier
// Création du tube int pipe_fds[2]; pipe(pipe_fds); // retourne 0 si OK, -1 si erreur int fr = pipe_fds[0]; // descripteur en LECTURE int fw = pipe_fds[1]; // descripteur en ÉCRITURE // Dans le shell Linux : ls | less → crée un tube entre ls et less
| Situation | Comportement read() |
|---|---|
| Tube non vide | Lit min(taille, TAILLE_BUF) caractères |
| Tube vide, pas d'écrivains | Retourne 0 (EOF) |
| Tube vide, des écrivains (bloquant) | Processus mis en sommeil jusqu'à données |
| Tube vide, des écrivains (non bloquant) | Retour immédiat: -1, errno = EAGAIN |
| Écriture, pas de lecteurs | SIGPIPE envoyé → terminaison processus |
Le tube nommé a une entrée dans le système de fichiers → accessible par des processus sans ancêtre commun (via chemin).
Identifiable par le type de fichier spécial p dans ls -l. Premier octet des droits = 'p'.
// Création du FIFO mkfifo("/tmp/fifo1", 0666); // Processus 1 (écrivain) int d_ecriture = open("fifo1", O_WRONLY); // Processus 2 (lecteur) — peuvent ne pas se connaître! int d_lecture = open("fifo1", O_RDONLY); // O_NONBLOCK → ouverture et I/O non bloquants int fd = open("somefifo", O_RDONLY | O_NONBLOCK);
Mécanisme de communication minimale entre processus. Utilisés par l'OS pour notifier les processus d'événements importants.
| Signal | Num | Type | Description |
|---|---|---|---|
| SIGKILL | 9 | AEF | Termine le processus (non interceptable) |
| SIGSTOP | 19 | DEF | Stoppe le processus (non interceptable) |
| SIGCONT | 18 | — | Continue l'exécution du processus |
| SIGCHLD | 17 | B | Un processus fils est terminé |
| SIGPIPE | 13 | A | Écriture dans un tube sans lecteur |
| SIGUSR1/2 | 10/12 | A | Signaux utilisateur (définis par l'app) |
| SIGALRM | 14 | A | Alarme (alarm(n) → SIGALRM dans n sec) |
// Envoyer signal signum au processus pid kill(pid, SIGTERM); // Envoyer signal à soi-même raise(signum); // ≡ kill(getpid(), signum) // Programmer envoi de SIGALRM dans n secondes alarm(5); // alarm(0) annule // Réactions possibles d'un processus à un signal: // 1. Ignorer le signal // 2. Action par défaut (terminaison, stop, core...) // 3. Handler personnalisé via signal() ou sigaction()
Peut communiquer sur la même machine ou sur des machines distantes. Base de Telnet, FTP, HTTP, WWW.
Style de communication (STREAM/DGRAM), espace de nommage (AF_UNIX, AF_INET), protocole.
socketpair(AF_UNIX, SOCK_STREAM, 0, sv); // SOCK_STREAM : flux continu (comme les tubes) write(sv[0], "hello", 5); read(sv[1], buf, 5); // SOCK_DGRAM : mode paquet (messages indivisibles) write(in, "ABC", 3); // file contient "ABC" write(in, "123", 3); // file contient "ABC","123" read(out, bf, 10); // retourne 3, bf="ABC" seulement!
| Capacité de la file | Comportement |
|---|---|
| Capacité zéro | Aucun message en attente → synchronisation rendez-vous. Émetteur bloqué jusqu'à récepteur prêt. |
| Capacité limitée (n) | File peut contenir n messages. Si pleine, émetteur se bloque. |
| Capacité illimitée | L'émetteur ne se bloque jamais. Non réaliste. |
Messages embrouillés : Détectés par CRC (somme de contrôle). L'OS retransmet le message original.
🧪 QCM — IPC
Deadlocks
Un ensemble de processus est en deadlock si chaque processus attend un événement que seul un autre processus de l'ensemble peut provoquer → blocage total et définitif.
Demande
Exploitation
Release
Si la requête ne peut être satisfaite immédiatement → processus bloque jusqu'à disponibilité
Une ressource est soit allouée à un seul processus, soit disponible. Impossible d'être partagée.
Un processus qui détient des ressources peut en demander d'autres, sans libérer celles qu'il possède.
Les ressources allouées ne peuvent être retirées de force. Elles sont libérées uniquement par le processus lui-même.
P0 attend une ressource de P1, P1 attend de P2, ..., Pn attend de P0 → cycle fermé.
Représentés par des cercles (P1, P2...)
Représentés par des carrés (R1, R2...). Les points à l'intérieur = instances.
La ressource est allouée au processus.
Le processus attend la ressource (bloqué).
| # | Stratégie | Principe | Usage pratique |
|---|---|---|---|
| 1 | Ignorer (Ostrich) | Prétendre qu'il n'y a pas de problème | UNIX/Windows : deadlocks rares, coût trop élevé pour les traiter |
| 2 | Détecter & Récupérer | Laisser arriver, détecter, puis corriger | Systèmes où les deadlocks sont tolérables temporairement |
| 3 | Éviter (Avoidance) | Allocation prudente (état sûr/non sûr) | Algorithme du Banquier de Dijkstra |
| 4 | Prévenir (Prevention) | Empêcher une des 4 conditions | Conception du système dès le départ |
- C[n×m] : Matrice allocations courantes. C[i,j] = nb ressources de type j tenues par Pi
- R[n×m] : Matrice demandes. R[i,j] = nb ressources de type j manquant à Pi
- A[m] : Vecteur disponibilités. A[j] = ressources de type j non attribuées
- E[m] : Vecteur existences. E[j] = total ressources de type j dans le système
- Chercher un processus Pi non marqué dont la rangée R[i] ≤ A (peut être satisfait)
- Si trouvé → ajouter C[i] à A (Pi va terminer et libérer ses ressources), marquer Pi, retour étape 1
- Si pas de tel processus → les processus non marqués sont en deadlock. Fin.
Retirer provisoirement une ressource à son propriétaire et l'attribuer à un autre. Dépend de la nature de la ressource.
Restaurer un état antérieur (point de reprise = checkpoint). Sauvegarde mémoire + état ressources.
Supprimer un ou plusieurs processus. Choisir un processus pouvant être redémarré sans conséquence (plus simple).
Il existe un ordre d'exécution (séquence sûre) dans lequel tous les processus peuvent terminer. L'état actuel ne mène pas à un deadlock.
Aucune séquence d'exécution garantissant la terminaison de tous. Ne signifie pas nécessairement un deadlock, mais à risque.
| Condition à briser | Méthode | Problème |
|---|---|---|
| Mutual Exclusion | Sérialiser les requêtes (ex: spooling imprimante → démon d'impression) | Pas toujours possible (certaines ressources sont intrinsèquement exclusives) |
| Hold & Wait | Demander toutes les ressources à l'avance avant de démarrer | Très difficile en pratique (allocation dynamique) + gaspillage de ressources |
| No Preemption | Permettre à l'OS de retirer des ressources de force | Dégrade le système. Possible seulement pour certaines ressources (CPU, mémoire) |
| Circular Wait | Numéroter les ressources. Demande uniquement par ordre croissant, ou une ressource à la fois. | Difficile de trouver une bonne numérotation pour toutes les ressources |
🧪 QCM — Deadlocks
Flashcards — Cliquez pour retourner
Testez votre mémorisation des définitions clés. Chaque carte montre un terme, cliquez pour voir la définition.
| Concept | Définition courte | Mot-clé |
|---|---|---|
| Race Condition | Résultat non déterministe selon l'ordre d'exécution | DANGER |
| Section Critique | Code manipulant une ressource critique | ATOMIQUE |
| Sémaphore binaire | Mutex : valeur 0 ou 1, exclusion mutuelle | INIT=1 |
| Sémaphore comptage | Gère N ressources identiques | INIT=N |
| wait(S) | S-- ; si S<0, bloquer | P / down |
| signal(S) | S++ ; si S≤0, réveiller un processus | V / up |
| Moniteur | Encapsulation SC + mutex géré par compilateur | synchronized |
| pipe() | Tube anonyme, unidirectionnel, processus liés | FIFO |
| mkfifo() | Tube nommé, visible dans FS, processus quelconques | PATH |
| SIGKILL | Signal 9, terminaison forcée, non interceptable | kill -9 |
| Deadlock | Tous les processus attendent indéfiniment | BLOCAGE |
| Hold & Wait | Condition 2 du deadlock : tenir + demander d'autres | GREEDY |
| Banker's Algorithm | Évitement deadlock via état sûr/non sûr | SAFE |
| Ostrich Algorithm | Ignorer les deadlocks (solution UNIX/Windows) | 🦦 |