Add synchronisation section
This commit is contained in:
parent
c0166072c1
commit
9c58721451
1 changed files with 564 additions and 0 deletions
564
content/systemes_exploitation/3-synchronisation/index.md
Normal file
564
content/systemes_exploitation/3-synchronisation/index.md
Normal file
|
@ -0,0 +1,564 @@
|
||||||
|
---
|
||||||
|
title: "Systèmes d'exploitation : Les outils de synchronisation"
|
||||||
|
date: 2021-09-24
|
||||||
|
tags: ["système", "sémaphore", "synchronisation", "thread"]
|
||||||
|
categories: ["Systèmes d'exploitation", "Cours"]
|
||||||
|
---
|
||||||
|
|
||||||
|
Dans ce chapitre, nous allons étudier différents problèmes (et solutions)
|
||||||
|
relatives à la synchronisation :
|
||||||
|
|
||||||
|
* l'exclusion mutuelle des sections critiques
|
||||||
|
* le problème des lecteurs - écrivains
|
||||||
|
* le problème des producteurs consommateurs
|
||||||
|
* le problème des points de rendez-vous
|
||||||
|
|
||||||
|
## L'exclusion mutuelle
|
||||||
|
|
||||||
|
Les processeurs savent juste faire des opérations de lecture / écriture de façon
|
||||||
|
atomique. Mais dans le cadre de l'incrémentation d'une variable comme nous
|
||||||
|
l'avions vu dans le chapitre traitant des processus légers
|
||||||
|
[en lpro]({{< ref"../../progsys/3-processus/index.md">}} "Les thread") ce n'est
|
||||||
|
pas suffisant. Il nous faut donc utiliser **l'exclusion mutuelle**.
|
||||||
|
|
||||||
|
L'algorithme de [Peterson][l_peterson] datant de 1981 utilise l'attente active
|
||||||
|
pour arriver à ses fins. Un problème se pose alors: le temps processeur est
|
||||||
|
gaspillé! De plus, cet algorithme est valable pour deux threads seulement.
|
||||||
|
|
||||||
|
```c
|
||||||
|
//Gary L. Peterson, 1981. Works with two processes : #0 and #1
|
||||||
|
bool flag [2] = { FALSE, FALSE };
|
||||||
|
unsigned turn = 0;
|
||||||
|
void enter_sc ()
|
||||||
|
{
|
||||||
|
flag[me] = TRUE;
|
||||||
|
turn = me;
|
||||||
|
wait (flag[1 – me] == FALSE or turn != me);
|
||||||
|
}
|
||||||
|
void exit_sc ()
|
||||||
|
{
|
||||||
|
flag[me] = FALSE;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Nous avons donc besoin d'une solution non seulement valable pour un nombre
|
||||||
|
indéfinis de fils d'exécutions, mais aussi plus efficace.
|
||||||
|
|
||||||
|
## Une solution matérielle
|
||||||
|
|
||||||
|
Vu du processeur, une opération atomique ne peut être interompue par une autre.
|
||||||
|
Les processeurs modernes en comportent un certain nombres. La solution viens
|
||||||
|
donc des fabricant de processeurs.
|
||||||
|
|
||||||
|
Pour forcer l'exclusion mutuelle, nous en avons besoin d'une seule:
|
||||||
|
`Test_and_Set`. C'est donc une instruction matérielle, voici une pseudo
|
||||||
|
implémentation en C:
|
||||||
|
|
||||||
|
```c
|
||||||
|
int test_and_set (int *verrou){
|
||||||
|
// -- Début de la partie atomique --
|
||||||
|
// Ceci est du pseudocode, montré ici juste pour illustrer le principe.
|
||||||
|
// S'il était utilisé normalement dans un compilateur,
|
||||||
|
// les garanties nécessaires d'atomicité, non-usage de cache,
|
||||||
|
// non-réorganisation par l'optimiseur etc.
|
||||||
|
// ne seraient pas fournies.
|
||||||
|
int old = *verrou;
|
||||||
|
|
||||||
|
// positionne le verrou à 1 pour dire qu'on veut occuper la SC (nb: ne
|
||||||
|
// change pas sa valeur si *verrou vaut déjà 1 car un autre processus
|
||||||
|
// est en SC)
|
||||||
|
*verrou = 1;
|
||||||
|
// -- Fin de la partie atomique --
|
||||||
|
|
||||||
|
// renvoie l'ancienne valeur (c'est-à-dire 1 (vrai) si et seulement si il
|
||||||
|
// y avait déjà un processus en SC à l'entrée dans test_and_set)
|
||||||
|
return old;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Ce code source est issu de la [page Wikipedia][l_testandset] sur Test and Set,
|
||||||
|
voyons maintenant cpmment l'utiliser:
|
||||||
|
|
||||||
|
```c
|
||||||
|
int lock = 0;
|
||||||
|
void enter_sc ()
|
||||||
|
{
|
||||||
|
while (test_and_set(&lock) == 1)
|
||||||
|
while (lock)
|
||||||
|
/* wait */ ;
|
||||||
|
}
|
||||||
|
void exit_sc ()
|
||||||
|
{
|
||||||
|
lock = 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Le problème d'accès à la section critique est réglé. Nous avons tout de même
|
||||||
|
toujours un problème d'attente active... Car le noyau ne peut savoir qui a poser
|
||||||
|
le verrou. Les threads attendant la libération de la section critique **seront
|
||||||
|
toujours "promus"** sur le processeurs et consomerons du temps. Il faut donc
|
||||||
|
trouver une solutions pour **endormir les threads attendant d'entrée en section
|
||||||
|
critique**.
|
||||||
|
|
||||||
|
La solution est donc un appel système permettant d'endormir (et réveiller) les
|
||||||
|
threads en attente. Car seul le noyau peut endormir les processus.
|
||||||
|
|
||||||
|
## Les sémaphores
|
||||||
|
|
||||||
|
On en a déjà parlé [en lpro]({{< ref"../../progsys/8_IPC">}} "Les IPC"). Ce sont
|
||||||
|
des outils de haut-niveau, implémentés dans le noyau. Il se compose d'une
|
||||||
|
structures contenant un entier positif et une liste de processus en attente et
|
||||||
|
de deux méthodes `P()` et `V()`. Il ont été inventés en 1962 par E. Dijsktra.
|
||||||
|
|
||||||
|
Les méthodes permettent :
|
||||||
|
* `P()`: attendre un jeton, le prendre lorqu'il devient disponible et continuer
|
||||||
|
son exécution.
|
||||||
|
* `V()`: remettre un jeton
|
||||||
|
|
||||||
|
Lorqu'un thread appelle `V()`, le sémaphore va reveiller les threads référencés
|
||||||
|
dans la liste d'attente.
|
||||||
|
|
||||||
|
Comme seul le noyau peut arrêter et reveiller des processus, les sémaphores sont
|
||||||
|
donc implémentés sur le noyau.
|
||||||
|
|
||||||
|
### Implementer un rendez-vous entre plusieurs processus.
|
||||||
|
|
||||||
|
Il faut faire en sorte que plusieurs processus s'attendent avant de continuer
|
||||||
|
leurs exécutions.
|
||||||
|
|
||||||
|
Voici le code implementant notre fonction barrière. Celle ci permet à plusieurs
|
||||||
|
processus de se donner un rendez-vous. On utilise des **sémaphore** pour
|
||||||
|
implementer une sorte de mutex.
|
||||||
|
|
||||||
|
```c {linenos=table}
|
||||||
|
// point 1
|
||||||
|
Semaphore wait[2](0), mutex[2](1);
|
||||||
|
int count[2] = { 0, 0 };
|
||||||
|
void barrier (int i)
|
||||||
|
{
|
||||||
|
// point 2
|
||||||
|
P(mutex[i]);
|
||||||
|
count[i]++;
|
||||||
|
|
||||||
|
if (count[i] < N) {
|
||||||
|
V(mutex[i]);
|
||||||
|
P(wait[i]);
|
||||||
|
} else {
|
||||||
|
count[i] = 0;
|
||||||
|
V(mutex[i]);
|
||||||
|
|
||||||
|
// point
|
||||||
|
for (int k=0; k < N-1; k++)
|
||||||
|
V(wait[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Explication de code :
|
||||||
|
* **point 1**: Utiliser des tableaux de 2 éléments permet de réutiliser notre
|
||||||
|
fonction `barrier()` pour fixer plusieurs rendez-vous. Il suffit alors
|
||||||
|
d'apeller à tous de rôle `barrier(0);` et `barrier(1);`. Sans ça un
|
||||||
|
processus un peu trop rapide pourrait prendre le sémaphore de la seconde
|
||||||
|
barrière et bloquer alors pa première.
|
||||||
|
* **point 2**: l'incrementation de notre compteur doit se faire dans un
|
||||||
|
*"mutex"* afin d'éviter que plusieurs processus y accèdent en même temps.
|
||||||
|
On relache notre sémaphore *"mutex"* ligne 11.
|
||||||
|
|
||||||
|
Dans la vraie vie, on n'utilise pas de barrière, on utilisera plutôt `signal()`
|
||||||
|
et `wait()`. Mais nous verrons ça plus tard.
|
||||||
|
|
||||||
|
### Le problème des producteurs / consommateurs avec des sémaphore
|
||||||
|
|
||||||
|
Le principe ici est d'implémenter une une structure de type FIFO partagée entre
|
||||||
|
plusiers processus. Cette structure a une capacité de `MAX` élements. Nous
|
||||||
|
allons utiliser deux sémaphores: un pour le producteur et un pour le
|
||||||
|
consommateurs.
|
||||||
|
|
||||||
|
Un producteur appelle la fonction `put(element)` et un consommateurs
|
||||||
|
`element get()`.
|
||||||
|
|
||||||
|
```c
|
||||||
|
#define MAX 8
|
||||||
|
semaphore cons(0), prod(MAX)
|
||||||
|
|
||||||
|
// Pour le consommateur
|
||||||
|
P(cons);
|
||||||
|
elemet = get();
|
||||||
|
V(prod);
|
||||||
|
|
||||||
|
// Pour le producteur
|
||||||
|
P(prod);
|
||||||
|
put(element);
|
||||||
|
V(cons);
|
||||||
|
```
|
||||||
|
|
||||||
|
Le fonctionnement est ici assez simple :
|
||||||
|
|
||||||
|
* **Pour le consomateur**:
|
||||||
|
1. il prend un jeton sur le sémaphore `cons` s'il est disponible (ou attends
|
||||||
|
qu'il le soit)
|
||||||
|
2. consomme l'élement
|
||||||
|
3. on relache celui sur `prod`. Cette dernière action permet de relancer
|
||||||
|
la production si la file était pleine (`prod` en attente).
|
||||||
|
* **pour le producteur**:
|
||||||
|
1. on décrémente la sur la production (`prod`). Cette
|
||||||
|
action permet d'arrêter la production s'il n'y a plus de jeton
|
||||||
|
disponible.
|
||||||
|
2. On produit ensuite l'élément
|
||||||
|
3. puis on relache le sémaphore `cons`. On réveille ansi notre consommateur
|
||||||
|
s'il dormait en attendant la disponibilité d'un élement
|
||||||
|
|
||||||
|
#### Multiple producteurs et consomateur
|
||||||
|
|
||||||
|
Dans la vraie vie, il y souvent plusieurs consomateurs / producteurs. Le
|
||||||
|
problème devient alors plus complexe...
|
||||||
|
|
||||||
|
Le code ressemble à celui ci-dessus, saut qu'il faut faire attention à ce qu'il
|
||||||
|
y ai un seul consmateir à la fois sur `get()` et un seul producteur sur
|
||||||
|
`put(element)`. nous allons donc rajouter deux *"mutex"* :
|
||||||
|
|
||||||
|
```c {linenos=table,hl_lines=[6,8,13,15]}
|
||||||
|
#define MAX 8
|
||||||
|
semaphore cons(0), prod(MAX), mutex_c(1), mutex_p(1);
|
||||||
|
|
||||||
|
// Pour le consomateir
|
||||||
|
P(cons)
|
||||||
|
P(mutex_v);
|
||||||
|
element = get();
|
||||||
|
V(mutex_v);
|
||||||
|
V(prod)
|
||||||
|
|
||||||
|
// Pour le producteur
|
||||||
|
P(prod);
|
||||||
|
P(mutex_c);
|
||||||
|
put(element);
|
||||||
|
V(mutex_p);
|
||||||
|
V(cons)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Problème des lecteurs rédacteurs
|
||||||
|
|
||||||
|
Ce problème ressemble à celui des producteurs / consommateurs.
|
||||||
|
|
||||||
|
* *lecteur*: processus qui lit des données sans les modifier. Il n'y a pas de
|
||||||
|
problème d'exclusion mutuelle : plusieurs lecteur peuvent lire la même
|
||||||
|
donnée.
|
||||||
|
* *rédacteur*: processus qui modifie des données. Exclusion avec non seulement
|
||||||
|
les autres rédacteurs mais aussi les lecteurs.
|
||||||
|
|
||||||
|
Voici le code pour les lecteurs:
|
||||||
|
|
||||||
|
```c {linenos=table,hl_lines=[8,12]}}
|
||||||
|
Semaphore write_token(1);
|
||||||
|
int n_reader = 0;
|
||||||
|
Semaphore mutex_r(1);
|
||||||
|
//Waiting Room help us to make our implememtatoion Fair
|
||||||
|
Semaphore wait_room(1);
|
||||||
|
|
||||||
|
// Reader
|
||||||
|
P(wait_room);
|
||||||
|
P(mutex_r);
|
||||||
|
// Pathfinder
|
||||||
|
// Equivalent to nbr++; if nbr == 1
|
||||||
|
if (++nbr == 1)
|
||||||
|
{
|
||||||
|
P(write_token);
|
||||||
|
}
|
||||||
|
V(mutex_r);
|
||||||
|
V(wait_room);
|
||||||
|
|
||||||
|
read();
|
||||||
|
|
||||||
|
P(mutex_r)
|
||||||
|
if(--nbr == 0) // last to leave
|
||||||
|
V(write_token);
|
||||||
|
V(mutex_r);
|
||||||
|
```
|
||||||
|
Pour les lecteur, il faut envoyer **un éclaireur** afin de savoir si un lecteur
|
||||||
|
est actif. Le problème est similaire à celui des producteur consommateurs sauf
|
||||||
|
qu'il faut savoir si notre lecteur est le premier *(ligne 12)*
|
||||||
|
|
||||||
|
De plus. afin que notre algorithme ne privilegie pas les lecteurs au
|
||||||
|
détriment des rédacteur; ils peuvent même ne jamais rendre la main aux
|
||||||
|
rédacteurs. Pour pallier au problème, nous devons mettre en place une **salle
|
||||||
|
d'attente** *(ligne 8)*.
|
||||||
|
|
||||||
|
```c {linenos=table,hl_lines=[8,12]}
|
||||||
|
P(wait_room);
|
||||||
|
P(write_token);
|
||||||
|
V(wait_room);
|
||||||
|
write();
|
||||||
|
V(write_token);
|
||||||
|
```
|
||||||
|
|
||||||
|
Ce code est plutôt clair et ne comporte rien de particulier.
|
||||||
|
|
||||||
|
## Les moniteurs d'Hoare
|
||||||
|
|
||||||
|
Les moniteurs sont des primitives de synchronisation initialement proposées dans
|
||||||
|
les langages objet. Il est utilisé actuellemt dans des langages tel que ADA ou
|
||||||
|
Java et implemente au sein de systèmes d'exploitation.
|
||||||
|
|
||||||
|
Le moniteur se positionne sur une classe et les mutexes sur ses methodes et sont
|
||||||
|
basés sur des variables condition. elle sont forcement privée et inaccessible à
|
||||||
|
l'exterieur de la classe.
|
||||||
|
|
||||||
|
```java
|
||||||
|
Monitor class m {
|
||||||
|
private int i = 1;
|
||||||
|
private condition c;
|
||||||
|
public method f() { // cette accolade signifie P(mutex)
|
||||||
|
code_before();
|
||||||
|
wait(c);
|
||||||
|
code_after();
|
||||||
|
} // et celle-ci V(mutex)
|
||||||
|
|
||||||
|
public method g() {
|
||||||
|
signal(c);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Il sont différent des *sémaphores*: il n'y a pas de jeton à prendre. Leur
|
||||||
|
implémentation dans les systèmes d'exploitation est différente, elle se fait par
|
||||||
|
les les types `mutex_t` et `cond_t`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
mutex_t m;
|
||||||
|
mutex_lock(mutex_t *m);
|
||||||
|
mutex_unlock(mutex_t *m);
|
||||||
|
|
||||||
|
cond_t c;
|
||||||
|
cond_wait(cond_t *c, mutex_t *m);
|
||||||
|
cond_signal(cond_t *c);
|
||||||
|
cond_bcast(cond_t *c);
|
||||||
|
```
|
||||||
|
|
||||||
|
Et voici un exemple d'utilisation:
|
||||||
|
|
||||||
|
```c
|
||||||
|
mutex_t m;
|
||||||
|
cond_t c;
|
||||||
|
|
||||||
|
void c(){
|
||||||
|
mutex_lock(&m)
|
||||||
|
if (...){
|
||||||
|
cond_wait(&c, &m);
|
||||||
|
}
|
||||||
|
mutex_unlock(&m);
|
||||||
|
}
|
||||||
|
|
||||||
|
void g(){
|
||||||
|
mutex_lock(&m);
|
||||||
|
...
|
||||||
|
cond_signal(&c);
|
||||||
|
...
|
||||||
|
mutex_unlock(&m);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### bonne pratiques
|
||||||
|
|
||||||
|
* Pas de variable condition à l'extérieur d'un moniteur, c'est aussi conseillé
|
||||||
|
pour les `cont_signal` et `cond_bcast`.
|
||||||
|
* Les mutex ont un propriétaire.
|
||||||
|
|
||||||
|
### Retour sur le problème du rendez-vous
|
||||||
|
|
||||||
|
L'idée est de bloquer les N-1 processus, le dernier (N) reveillera tous les
|
||||||
|
autres.
|
||||||
|
|
||||||
|
```c
|
||||||
|
mutex_t m;
|
||||||
|
cond_t wait;
|
||||||
|
int nb = 0;
|
||||||
|
|
||||||
|
void barrier ()
|
||||||
|
{
|
||||||
|
// notre variable condition et bien "protégée" par un mutex
|
||||||
|
mutex_lock(&m);
|
||||||
|
nb++;
|
||||||
|
|
||||||
|
// on stoppe les n-1 processus
|
||||||
|
If (nb < N)
|
||||||
|
cond_wait (&wait, &m);
|
||||||
|
else {
|
||||||
|
// le dernier réveille tous les autrespar un Bcast
|
||||||
|
cond_bcast (&wait);
|
||||||
|
// et on positionne nb à 0 permettant ainsi de réutiliser cette fonction
|
||||||
|
// barrière.
|
||||||
|
nb = 0;
|
||||||
|
}
|
||||||
|
mutex_unlock(&m);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
En plus d'être plus simple, cette fonction est réutilisable de multiples fois
|
||||||
|
sans le petit *"hack* des tableaux utilisé pour les sémaphore.
|
||||||
|
|
||||||
|
### Producteur / consomateurs avec un moniteur
|
||||||
|
|
||||||
|
tout comme les barrières, le code est ici bien plus simple. Et bien entendu il
|
||||||
|
n'y a pas d'attente active...
|
||||||
|
|
||||||
|
Dans les codes ci-dessous, il est préféfable d'utiliser `while (nbe ...)` plutôt
|
||||||
|
qu'un `if (nbe ...)`. (Mais je ne sais plus pourquoi...)
|
||||||
|
|
||||||
|
Voici les variables nécessaires aux producteurs et au consomate
|
||||||
|
|
||||||
|
```c
|
||||||
|
#define MAX 8
|
||||||
|
mutex_t m;
|
||||||
|
cond_t cons, prod;
|
||||||
|
int nbe = 0;
|
||||||
|
```
|
||||||
|
|
||||||
|
#### pour le producteur
|
||||||
|
|
||||||
|
```c
|
||||||
|
mutex_lock (&m);
|
||||||
|
|
||||||
|
/* si notre file est pleine, alors le thread passe en sommeil
|
||||||
|
et attend le reveil par un cond_signal */
|
||||||
|
while (nbe == MAX) {
|
||||||
|
cond_wait(&prod);
|
||||||
|
}
|
||||||
|
put(element);
|
||||||
|
nbe++;
|
||||||
|
cond_signal(&cons)
|
||||||
|
mutex_unlock (&m);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### pour les consomateurs
|
||||||
|
|
||||||
|
```c
|
||||||
|
mutex_lock (&m);
|
||||||
|
|
||||||
|
/* Et inversement si notre file est vide, rien à consommer, on
|
||||||
|
attend un reveil par un cond_signal dès qu'il y a production */
|
||||||
|
while (nbe == 0) {
|
||||||
|
cond_wait(&cons)
|
||||||
|
}
|
||||||
|
element = get();
|
||||||
|
nbe--;
|
||||||
|
cond_signal(&prod);
|
||||||
|
mutex_unlock (&m);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Les lecteurs / rédateurs
|
||||||
|
|
||||||
|
Nous commençons ici par créer une structure et l'affecter à une variable
|
||||||
|
`my_lock`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef struct{
|
||||||
|
mutex_t m;
|
||||||
|
cond_t c_read, c_write;
|
||||||
|
unsigned nb_reader = 0;
|
||||||
|
unsigned nb_writer = 0;
|
||||||
|
} rwlock_t;
|
||||||
|
|
||||||
|
rwlock_t mylock;
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Du côté des lecteur
|
||||||
|
|
||||||
|
```c
|
||||||
|
void rwl_readlock(rwlock_t *l)
|
||||||
|
{
|
||||||
|
mutex_lock(&l->m);
|
||||||
|
|
||||||
|
/* tout comme le problème des produteurs / consomateurs, un
|
||||||
|
while est préférable ici */
|
||||||
|
while(l->nbw > 0) {
|
||||||
|
cond_wait(&l->cr, &l->m);
|
||||||
|
}
|
||||||
|
l->nbr++;
|
||||||
|
mutex_unlock(&l->m);
|
||||||
|
}
|
||||||
|
|
||||||
|
void rwl_readunlock(rwlock_t *l)
|
||||||
|
{
|
||||||
|
mutex_lock(&l->m);
|
||||||
|
l->nbr--;
|
||||||
|
if(l->nbr == 0) {
|
||||||
|
cond_signal(&l->cw);
|
||||||
|
}
|
||||||
|
mutex_unlock(&l->m);
|
||||||
|
}
|
||||||
|
|
||||||
|
Et voici son l'utilisation :
|
||||||
|
|
||||||
|
```c
|
||||||
|
rwl_readlock(mylock);
|
||||||
|
read();
|
||||||
|
rwl_readunlock(mylock);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### du côté des rédacteurs
|
||||||
|
|
||||||
|
```c
|
||||||
|
void rwl_writelock(rwlock_t *l)
|
||||||
|
{
|
||||||
|
mutex_lock (l->m);
|
||||||
|
/* Comme ces deux nombres sont des entiers strictement positifs
|
||||||
|
on peut utiliser l'adition afin d'attendre en cas de présence
|
||||||
|
de lecteurs ou de rédacteur.
|
||||||
|
On évite ainsi une double condition :
|
||||||
|
while ( l->nb_reader > 0 || l->nb_writer > 0) */
|
||||||
|
|
||||||
|
while( l->nb_reader + l->nb_writer > 0 ) {
|
||||||
|
cond_wait (&l->cw, &l->m);
|
||||||
|
}
|
||||||
|
l->nbw++;
|
||||||
|
mutex_unlock (&l->m);
|
||||||
|
}
|
||||||
|
|
||||||
|
void rwl_writeunlock(rwlock_t *l)
|
||||||
|
{
|
||||||
|
mutex_lock (l->m);
|
||||||
|
l->nbw--;
|
||||||
|
cond_signal (&l->cw);
|
||||||
|
rwl_writeunlock (&cool_lock);
|
||||||
|
|
||||||
|
/* On utilise le bcast pour les lecteur car on peut *tous*
|
||||||
|
les réveiller (+ieurs lecteurs possibles en même temps */
|
||||||
|
cond_bcast(&l->c_read);
|
||||||
|
/* mais on ne reveille qu'un seul écrivain */
|
||||||
|
cond_signal(&l->c_write);
|
||||||
|
mutex_unlock (&l->m);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Et voici son utilisation:
|
||||||
|
|
||||||
|
```c
|
||||||
|
rwl_writelock(mylock);
|
||||||
|
write();
|
||||||
|
rwl_writeunlock(mylock);
|
||||||
|
```
|
||||||
|
|
||||||
|
## conclusion
|
||||||
|
|
||||||
|
Les sémaphores et moniyeurs sont implémentés au niveau du système d'exploitation
|
||||||
|
et utilient le matériel sous-jacent (`test_and_set`). Ces appels sont
|
||||||
|
**coûteux**, nécessitent des **changements de contexte**. Dans les systèmes
|
||||||
|
modernes, on leur préfèrera un **mix de primitive de synchronisation et de
|
||||||
|
polling (attente de variable)**. Ainsi un thread attendra une valeur / variable
|
||||||
|
pendant quelques cycles et se bloquera si elle n'est pas disponible.
|
||||||
|
|
||||||
|
Les moniteurs de Hoare sont en général préférés par les programmeurs cas comme
|
||||||
|
on l'a vu, ils sont plus simple à implémenter.
|
||||||
|
|
||||||
|
Il eest intéressant aussi de parler des **FUTEX**, apparus en 2003 sur Linux et
|
||||||
|
plus tard sous Windows 8 (sous l'appelation `wait_on_address` et brevete en
|
||||||
|
2013).
|
||||||
|
|
||||||
|
Ils utilisent des opétations atomiques sur des variables entières de 32bits *en
|
||||||
|
espace utilisateur* et deux operation bloquante si nécessaires:
|
||||||
|
|
||||||
|
```c
|
||||||
|
sys_futex(&var, FUTEX_WAIT);
|
||||||
|
sys_futex (&var, FUTEX_WAKE);
|
||||||
|
```
|
||||||
|
|
||||||
|
[l_peterson]:https://fr.wikipedia.org/wiki/Algorithme_de_Peterson
|
||||||
|
[l_testandset]:https://fr.wikipedia.org/wiki/Test-and-set
|
Loading…
Add table
Add a link
Reference in a new issue