Files
Yggdrasil/infotro/PTR/PCO.md
T

8.5 KiB
Raw Blame History

Programmation concurrente

Un programme concurrent est un programme non séquentiel. Soit l'expression arithmétique suivante : (a+b) * (c+d).

Programmation séquentiel

  1. x = (a+b)
  2. y = (c+d)
  3. x*y

Programmation concurrente

  1. x = (a+b) \qquad y = (c+d)
  2. x*y

La programmation concurrente sur 1 CPU permet d'exploiter pleinement les périphériques parallèlement au processeur. Cela permet d'éviter de bloquer toutes les applications lorsqu'une seule est en attente d'une réponse.

La programmation concurrente sur plusieurs CPU permet en plus un gain de temps.

Exclusion mutuelle

un programme est une entité statique un processus est une entité dynamique

Les processus ne sont jamais totalement indépendants. Ils partagent des ressources (mémoire, fichiers, périphériques, variable, etc.)

Ceci peut conduire à des erreurs et doit donc être controlé.

  • Section critique : partie du code qui accède à une ressource partagée.
  • Exclusion mutuelle : un seul processus à la fois peut accéder à la section critique.
  • Ressource critique : ressource qui doit être accédé en exclusion mutuelle

On pourrait éviter les problèmes d'exclusion mutuelle si on pouvait assurer que deux processus n'exécute jamais en même temps une section critique. On peut résoudre ce problème à l'aide de mutex ou sémaphore.

Exclusion mutuelle par attente active

type processNum = 1..2;

module CS;                                          /* Section critique */
    defines Enter, Leave;                           /* Primitive */   

    var
        request : array processNum of boolean;
        tour    : processNum;
    procedure Enter(num: processNum);
    begin
        request[num] := true;                       /* Demande d'accès */
        tour := 3 – num;                            /* Autre processus */
        repeat
        /* Attendre que l'autre processus ait fini avec "not request" */
        /* Priorité si les deux processus demandent en même temps à la section critique
           grâce à "tour = num" */
        until (not request[3-num]) or (tour = num)
    end Enter;

    procedure Leave(num: processNum);
    begin
        request[num] := false
    end Leave;

    begin                                           /* Initialisation */
        request[1] := false;
        request[2] := false
    end CS;
        
/***********************************************************************/

process P1;
    uses CS, Console;
begin
    CS.Enter(1);
    Console.Write(”Error on hard disk !”);
    CS.Leave(1);
end P1;

/***********************************************************************/

process P2;
    uses CS, Console;
begin
    CS.Enter(2);
    Console.Write(”Printer not ready !”);
    CS.Leave(2);
end P2;
  • si les processus P1 et P2 ne tentent pas de pénétrer simultanément dans la section critique, la variable request[3-num] est fausse et l'entrée en section critique est autorisée
  • si par contre les processus P1 et P2 tentent de pénétrer simultanément dans la section critique, la variable tour permet d'arbitrer entre les deux demandes. La variable tour (à la ligne 15) indique le numéro du processus élu

Cette méthode peut facilement être généralisée à n processus. Il suffit de faire tour := n+1 – num et request[n+1-num].

Cette solution n'est pas satisfaisante, car elle consomme beaucoup de ressources CPU. En effet, un processus qui attend l'accès à la section critique est en attente active. Il est donc en boucle constante et consomme beaucoup de ressources CPU.

Exclusion mutuelle par masquage des interruptions

Masquer les interruptions pour résoudre le problème de l'exclusion mutuelle. Plus d'interruption => plus de commutation de processus => plus de risque de violer l'exclusion mutuelle.

Le danger avec cette solution est le risque de perte d'information provenant des périphériques. Cela ne peut être une bonne solution que si la section critique est très courte. Il est dangereux de laisser la possibilité de masquer les interruptions à l'utilisateur.

Instruction TAS (TestAndSet)

L'exclusion mutuelle par masquage des interruptions n'est pas possible dans une architecture multiprocesseur. Il est possible de résoudre le problème de l'exclusion mutuelle en utilisant l'instruction TAS. Elle permet de tester et de modifier une variable en une seule instruction assembleur.

Mutex

Le mutex est utilisé pour bloquer l'accès simultané aux sections critiques avec les primitives suivantes :

  • block() : Il laisse passer un seul processus à la fois. Si la section est déjà bloquée, le processus et mis dans une file d'attente.
procedure Block(var v:lock);
begin
  if v.state = OPEN then
    v.state := CLOSE
  else
    block the process in the list "v.waiting"
  end if
end Block;
}
  • release() : Il libère la section critique, si la file d'attente est vide. Sinon, il passe la main au processus suivant.
procedure Release(var v:lock);
begin
  if queue "v.waiting" not empty then
    unblock first process in the list "v.waiting"
  else
    v.state := OPEN
  end if
end Release;
}

Ces procédures sont elles même des sections critiques. Elles sont cepandant très courte et ne contiennent aucunes entrées-sorties. Elles peuvent donc être implémentées à l'aide d'une Exclusion mutuelle par masquage des interruptions ou d'une instruction TAS.

Les Mutex restent un outil primitif. Ils ne s'appliquent pas sur des problèmes plus complexes.

Sémaphore

Le sémaphore est une généralisation du mutex. Il permet de bloquer l'accès à une ressource partagée par plusieurs processus. Il limite l'accès à plusieurs même ressources utilisées par plusieurs processus.

Par exemple : les processus ont besoin d'imprimer et il y a plusieurs imprimantes à disposition. Le sémaphore va limiter l'accès au nombre d'imprimantes disponibles.

Les primitives de sémaphores sont :

  • P (Passeren = passer) appelé aussi wait, up, get ou lock
procedure P(var s:semaphore);
begin
  s.num := s.num – 1;
  if s.num < 0 then
    block the process at the end of the list "s.waiting"
  end if
end P;
  • V (Vrijgeven = libérer) appelé aussi signal, down, release ou unlock
procedure V(var s:semaphore);
begin
  s.num := s.num + 1;
  if s.num <= 0 then
    unblock first process in the list "s.waiting"
  end if
end V;

À l'initialisation, le sémaphore est initialisé au nombre de mêmes ressources disponibles. Ou alors, il est initialisé à 1 pour un mutex. Ou alors, il est initialisé à 0 pour un sémaphore binaire (file d'attente).

Interblocage

L'interblocage est un problème qui peut survenir lorsqu'un ensemble de processus se bloquent mutuellement. On appelle aussi cela un DEADLOCK.

Résolution reader / writter

Le but est qu'il peut avoir plusieurs processus qui puisse lire la même ressource et aucun modifier la ressource. Sinon, un processus peut modifier la ressource et aucun peut lire.

void BeginRead(void) {
	osSemaphoreAcquire(rw, osWaitForever);
	osSemaphoreAcquire(mutexNbReader, osWaitForever);   // ENTER CRITICAL SECTION
	nbReader++;
	if(nbReader == 1) osSemaphoreAcquire(w, osWaitForever);
	osSemaphoreRelease(mutexNbReader);                  // EXIT CRITICAL SECTION
	osSemaphoreRelease(rw);
}

void EndRead(void) {
	osSemaphoreAcquire(mutexNbReader, osWaitForever);   // ENTER CRITICAL SECTION
	nbReader--;
	if(nbReader == 0) osSemaphoreRelease(w);
	osSemaphoreRelease(mutexNbReader);                  // EXIT CRITICAL SECTION
}

void BeginWrite(void) {
	osSemaphoreAcquire(rw, osWaitForever);
	osSemaphoreAcquire(w, osWaitForever);
}

void EndWrite(void) {
	osSemaphoreRelease(w);
	osSemaphoreRelease(rw);
}

Synchronisation

Etat d'un processus :

img_1.png

Le processeur possède une liste de descripteur caractérisant chaque processus. Ces descripteurs de processus qui contiennent le registre de status et un pointeur sur sa stack. Ils sont utilisé pour faire des switching context :

  1. Le noyau va sauvegarder les registres de travail du processus en cours sur la stack.
  2. Il va sauvegarder aussi le registre de status et le pointeur de stack dans la liste de descripteurs.
  3. Il va prendre le descripteur du prochain processus et utiliser ses registres.
  4. Le processus continue jusqu'au nouveau switching context.