You are here: Tutoriels » L'Ada » Le multi-tache

Lancer plusieurs threads

En Ada, il est très facile de lancer plusieurs tâches simultanément : il faut déjà déclarer la tâche : task Ma_Tache; ensuite, il faut dire ce qu'elle fait en respectant la syntaxe suivante :
task body Ma_Tache is
  ...
begin
  ...
end Ma_Tache;

et c'est tout, rien de plus facile.

Attendre un peu

Souvent, une tâche consiste à faire quelque chose, attendre un peu, refaire le quelque chose, attendre un peu, etc, etc... Pour attendre un peu, on pourrait faire une grosse boucle qui ne fait rien, mais le processeur serait très utilisé et donc les autres tâches rameraient. Pour nous aider, Ada propose un package "Ada.Real_Time" qui contient une méthode "Clock" qui renvoie l'heure qu'il est. A cette heure, on peut ajouter des "Time_Span" qu'on obtient en convertissant un nombre flotant de secondes avec la fonction "To_Time_Span". Ensuite, on dit à Ada de ne rien faire jusqu'à cette nouvelle heure. Cela donnedelay until Clock + To_Time_Span (1.2); pour attendre 1.2 s sans rien faire.

Les problèmes de synchronisation

Le problème lorsqu'on lance plusieurs tâches, en même temps, c'est la synchonisation : imaginons qu'on dispose d'un robot avec une fonction pour le faire avancer. 2 tâches veulent le faire bouger mais bien sûr, il ne faut pas que les 2 tâches le fasse bouger en même temps. Pour éviter cela, on déclare un Boolean Busy qui sera à True lorsque le robot bouge et à Zero le reste du temps. Chaque tâche aura un code qui ressemblera à ça :
if not Busy then
  Busy = True;
  Move;
  Busy = False;
end if;

Malheureusement, si les 2 tâches s'exécutent en même temps, il se peut que les 2 tâches lisent la valeur de Busy en même temps, elles trouveront donc False toutes les 2. Ensuite, toutes les 2 vont mettre Busy à False puis faire bouger le robot, ce qu'on voulait éviter.

La solution en Ada pour éviter ça est les objets "protected"

Les protected

Un protected est un objet qui contient des variables (non accessibles directement) et des fonctions/procédures. Le fait que ces procédures soit dans un protected assure qu'une seule tâche peut l'executer à tout moment. Les autres tâches attendent que celle qui est en cours ait fini d'exécuter la procédure.

Comme pour les tâches, il faut déclarer les objets protected puis les implémenter. Exemple :
protected Busy is
  procedure Set_Value (Value : Boolean);
  function Value return Boolean;
private
  Current_Value : Boolean;
end Busy;

protected body Busy is
  procedure Set_Value (Value : Boolean) is
  begin
    Current_Value := Value;
  end Set_Value;
  function Value return Boolean is
  begin
    return Current_Value;
  end Value;
end Busy;

 Cet exemple de protected ne sert à rien : il faut rajouter une petite fonction :
function Can_Move return Boolean is
  if not Current_Value then
    Current_Value := True;
    return True;
  else
    return False;
  end if;
end Can_Move;

  et là, ça compile pas frown, sad smileprotected function cannot modify protected objectEn effet, j'ai dit une petite bêtise plus haut : dans un protected, une procédure sera executée par une seule tâche mais une fonction peut être executée par plusieurs tâches en même temps, sous reserve qu'il n'y ait pas de tâche en train d'executer une procédure. L'avantage, c'est la rapidité : pas la peine d'attendre pour executer des fonctions. Le désavantage, c'est que pour que ca marche, les variables du protected sont read-only dans les fonctions.

Qu'a cela ne tienne, on va mettre une procédure :
procedure Can_Move (Result : out Boolean) is
  if not Current_Value then
    Current_Value := True;
    Result := True;
  else
    Result := False;
  end if;
end Can_Move;

et voilà, on dispose d'un moyen pour s'assurer qu'une seule tache va déplacer notre robot.

Les "entry"

Notre protected dispose d'une méthode "Can_Move" qui nous dis si on peut y aller. Maintenant, si on ne peut pas, que fait-on ? On attend un peu et on redemande... Cela n'est pas très optimal : si jamais l'autre tache finie juste après notre teste, il faut attendre quand même. Pour ne pas perdre trop de temps, on va mettre un delai d'attente faible, ce qui va impliquer une plus grosse charge CPU.

Pour résoudre ce problème, il existe un méchanisme : les "entry". Une "entry", c'est comme une procédure sauf que pour s'executer, elle attend qu'une condition soit vraie. Dès que la condition devient vraie et qu'il n'y a plus de tache qui execute des fonctions/procédures du protected, le code de l'entry est executé.

Exemple:
protected Busy is
  procedure Set_Value (Value : Boolean);
  function Value return Boolean;
  entry Wait_Until_Ready;
private
  Current_Value : Boolean;
end Busy;
protected body Busy is
  ...
  entry Wait_Until_Ready when not Current_Value is
  begin
    Current_Value := True;
  end Wait_For_Ready;
end Busy;