Cours et TD du 23/11/2023 et 30/11/2023
Je n’ai rien suivi désolé, le cours vient d’ailleurs

Sémaphores

Maintenant, supposons que l’on veut les implémenter. On doit utiliser quelque chose qui va être de plus bas niveau qui va assurer l’exclusion mutuelle, car ces primitives sont supposées atomique.

Pour implémenter ça, il faut utiliser, au plus bas niveau, quelque chose qui permet un accès atomique à une certaine variable. On a besoin de primitives de type T & S = Test and Set.

T & S d’une variable booléenne “lock”, ça revient à faire :

Test and Set(lock) {
  v := lock
  lock := true
  return v
}

qui est atomique (a.k.a offert par le matériel)

Si la valeur est 0, c’est bon, on a pris le lock et la valeur passe de 0 à 1, le prochain qui va tester va voir qu’il obtient une valeur de 1.
Donc : c’est prendre la valeur et en même temps la modifier : si elle est 0, elle passe à 1.
Si elle est à 1 : on attend ; avec ça on peut faire de l’attente active : attendre que le lock en paramètre devient 0.

Du coup, on aura un lock sur notre sémaphore S, il faut s’assurer que cela se passe de manière atomique (Test and Set).

Dans certains langages : on a une notion qui permet à une certaine section de s’exécuter de manière atomique. Alors cela passe à la charge du compilateur…

Exemple d’implémentation

l = false # le lock est libre (pas pris)

begin
  wait(S)
  done := false
  do
    # on attend le lock pour accéder à S
    # on attend que le retour de T&S soit false (l = false)
    if (¬ T&S(l)) then # quand on fait T&S on avait 0, maintenant il devient 1
      if (S>0) then # on termine la boucle, on ne cherche plus
        done := true
        S--
      endif
      l := false # lâche le lock pour que d'autres accèdes à S
    endif
  while (¬ done)
end

Implémentation de signal(S)

  • simple
Signal(S) # faut qu'il décrémente le S
# dit qu'il y a la possibilité d'incrémenter le S, il lâche le lock et s'en va
while(T&S(l)) do {}
S++
l := false

Exclusion mutuelle

Cela est vrai au bas niveau.

Les moniteurs

Différence avec les sémaphores

On peut imaginer que le moniteur lui-même a une file d’attente, et ensuite, ils entrent, un par un.
monitvssema

Simulation des sémaphores avec les moniteurs

Pour ne pas mélanger les 2 types de wait et signal, pour les sémaphores, on va utiliser P() et V()
Un moniteur a une variable à l’intérieur, et on va avoir les 2 opérations P() et V()
sim
On va définir notre moniteur Sem-simul (Sémaphore simulé). On va définir une variable et une condition.

Moniteur Sem-simul # Simulation d'un sémaphore binaire
var busy : boolean = false
var notbusy : condition

Procédure P
begin
  if busy then wait(notbusy) endif
  busy := true
end

Procédure V
begin
  busy := false
  signal(notbusy)
end

Donc, avec les moniteurs, on peut faire tout ce qu’on peut faire avec les sémaphores.

Producteur - Consommateur avec les moniteurs

prodconsom

prodconsom
On peut commencer, dans un premier temps, par un buffer infini où l’on peut toujours ajouter des éléments
buffer_infini

Moniteur Buffer

b        : array[0, -] # tableau non borné de int
in       : integer = 0
out      : integer = 0
notempty : condition

Procédure append(v : integer)
begin
  b[in] := v
  in := in + 1
  signal(notempty) # réveiller les processus en attente (s'il y en a)
end

Procédure take : return integer # soit le buffer est non-vide : alors on peut prendre un élément
                                # soit le buffer est vide : faut bloquer le processus
begin
  if (in = out) then # si les 2 pointeurs se croisent = le buffer est vide
    wait(notempty)
  endif
  tmp := b[out]
  out := out + 1
  return tmp
end

Vu que le buffer est non borné, le seul problème que l’on doit gérer est la situation ou le buffer est vide.

Producteur - Consommateur : buffer borné

Moniteur Buffer

n        : integer = 0 # nombre d'éléments dans le buffer
notfull  : condition
b        : array[0, B] # tableau borné de 0 à B de integer
in       : integer = 0
out      : integer = 0
notempty : condition

Procédure append(v : integer)
begin
  if (n = B + 1) then # on vérifie que c'est pas plein
    # si c'est plein on attend
    wait(notfull)
  endif
  b[in] := v
  in := in + 1
  if (n = B + 1) then # si on est au bout du buffer
    # on revient au début (car buffer circulaire)
    in := 0
  endif
  n := n + 1 # on incrémente le nombre d'éléments du buffer
  signal(notempty) # réveiller les processus en attente (s'il y en a)
end

Procédure take : return integer # soit le buffer est non-vide : alors on peut prendre un élément
                                # soit le buffer est vide : faut bloquer le processus
begin
  if (n = 0) then # si les 2 pointeurs se croisent = le buffer est vide
    wait(notempty)
  endif
  tmp := b[out]
  out := out + 1
  if (out = B + 1) then # si on est au bout du buffer
    # on revient au début car buffer circulaire
    out := 0
  endif
  n := n - 1
  signal(notfull) # s'il fait `signal`, un autre processus va se réveiller et `take()`
                  # va pas faire return, lui permettant de faire return plus tard
  return tmp
end