• Aucun résultat trouvé

Chapitre 3 : Programmes Ada Concurrents

7. Partage de Données Entre Tâches

En utilisant une tâche pour permettre multiple tâches d'accéder à une pile commune comme l'exemple décrit ci-dessus est une manière très raffinée et chère de partager des données. Il signifie qu'il doit y avoir une extra tâche pour gérer la pile en moitié avec les autres tâches qui veulent l'utiliser, et un rendez-vous est exigé pour accéder à la pile. Un rendez-vous est une opération relativement prolongée, ainsi il ajoute tout à fait de grands frais généraux à ce qui serait autrement un appel assez simple de procédure. On pourrait bien sûr déclarer la pile dans la même portée que les types de tâche qui doivent y avoir accès, mais c'est extrêmement risqué.

procedure Pop (Stack : in out Stack_Type; Item : out Item_Type) is

begin

Item := Stack.Body(Stack.Top); -- 1 Stack.Top := Stack.Top - 1; -- 2

exception when Constraint_Error => raise Stack_Underflow; end Pop;

Cela montre comment Pop est implémenté en utilisant un tableau. Dans un environnement où seulement une tâche exécute ce code, c'est parfaitement sûr. Si plus qu'une tâche l’exécute simultanément, les deux tâches pourraient exécuter l’instruction 1 en même temps, alors toutes les deux peuvent avoir une copie du même item. Quand elles exécutent l’instruction 2, Stack.Top pourrait être décrementé deux fois ou les deux tâches pourraient retrouver la même valeur de Stack.Top, soustrairent 1 et stockent ensuite le résultat dans Stack.Top, alors que Stack.Top semblera avoir été decrementé seulement une fois. En d'autres termes, le résultat sera complètement imprévisible (unpredictable) puisqu'il dépend du rapport précis de synchronisation entre les deux tâches. L'imprévisibilité (Unpredictability) sur cette échelle est rarement une bonne propriété pour les systèmes informatiques. La morale de l'histoire est que les tâches ne devraient jamais accéder à des données externes ; elles devraient seulement accéder à leurs propres objets locaux.

Pour venir au bout de ce problème, Ada permet à des données d'être encapsulées dans un enregistrement protégé qui garantit que cette sorte de situation ne peut pas surgir. Un enregistrement protégé est un type de données passif plutôt qu'un type actif comme une tâche, ainsi les coûts d'un rendez-vous et l’ordonnacement d’une extra

tâche sont évités. Des enregistrements protégés sont divisés en une spécification et un corps, juste comme une tâche. Les spécifications contiennent une partie visible qui déclare un ensemble de fonctions, procédures et des entrées. Les tâches sont permises d'appeler ces entrées comme une partie privée qui contient des données à protéger. Voici un enregistrement protégé qui encapsule une pile de nombres entiers :

protected type Shared_Stack_Type is procedure Push (Item : in Integer); entry Pop (Item : out Integer); function Top return Integer; function Size return Natural; function Empty return Boolean; private

package Int_Stacks is new JE.Stacks (Integer); Stack : Int_Stacks.Stack_Type;

end Shared_Stack_Type;

Stack : Shared_Stack_Type; -- declare an instance of Shared_Stack_Type

Comme pour les tâches, nous pouvons déclarer un seul enregistrement protégé d’un type anonyme en eliminant le mot type :

protected Shared_Stack_Type is ... ; -- same as: protected type ???; -- Shared_Stack_Type : ???;

Le corps fournit les implémentations des fonctions, des procédures et des entrées déclarées dans les spécifications. La différence entre les trois types d'opération est qu'on permet seulement à des des fonctions de lire les valeurs des items d'informations privées (private); de tels items appraîssent à la fonction comme des constantes et la fonction ne peut pas les changer. Puisqu'il est sûr pour plusieurs tâches de lire les mêmes données en même temps, des multiples tâches sont autorisées à exécuter des fonctions dans un objet protégé en même temps. Des procédures et des entrées sont permises de changer les informations privées, ainsi une tâche ne peut appeler aucune des opérations protégées tandis que d’autres tâches exécutent un appel de procédure ou d'entrée. La différence entre une procédure et une entrée est que les entrées ont des gardes qui agissent comme les gardes des instructions accept; une entrée peut seulement être exécutée quand sa garde est vraie, et n’importe quelle tâche qui appelle une entrée dont la garde est fausse sera suspendue jusqu'à ce que la garde devienne vraie (à quel point l'appel d'entrée peut alors être exécuté). Dans le type protégé Shared_Stack_Type, il y a trois fonctions (Top, Size et Empty) qui n'affectent pas la pile privée qu'il contient. Les tâches pourront appeler ces fonctions aussi longtemps qu'aucun appel de procédure ou d'entrée n'est en évolution; s'il y a déjà un appel de procédure ou d'entrée en évolution, on ne permettra pas à la tâche appellante la fonction de procéder jusqu'à ce que l'appel en cours finisse l'exécution. Il y a une procédure (Push) ; chaque tâche appelante Push devrait attendre jusqu'à ce que tous les autres appels en cours finissent l'exécution. Il y a une entrée (Pop) ; n’importe quelle tâche appelante Pop devrait attendre, non seulement jusqu'à ce que tous les autres appels en cours

finissent l'exécution, mais également jusqu'à ce que la garde d'entrée soit vraie. Voici le corps protégé, la condition de garde pour l'entrée Pop est indiquée après la liste de paramètres entre when et is :

protected body Shared_Stack_Type is

procedure Push (Item : in Integer) is begin Int_Stacks.Push (Stack,Item); end Push; entry Pop (Item : out Integer)

when not Int_Stacks.Empty (Stack) is begin Int_Stacks.Pop (Stack,Item); end Pop; function Top return Integer is begin return Int_Stacks.Top (Stack); end Top;

function Size return Natural is begin return Int_Stacks.Size (Stack); end Size; function Empty return Boolean is begin return Int_Stacks.Empty (Stack); end Empty; end Shared_Stack_Type;

Ainsi, autant de tâches que possible peuvent inspecter simultanément l’item en top de la pile, lisent la taille de la pile ou examinent si elle est vide aussi longtemps qu’auccune autre tâche est entrain d’empiler ou de dépiler un item. Le dépilement d’un item est seulement autorisé si la pile n'est pas vide ; si elle est vide la tâche appelante devra attendre jusqu'à ce que d’autres tâches appelent Push. Les appels de Push et de Pop avanceront seulement si l’enregistrement protégé n'est pas en cours d’utilisation par une autre tâche.

8. Conclusion

Dans ce chapitre, nous avons introduit le langage Ada. Nous avons mis l’accent en particulier sur la notion de ‘multitâche’ dans ce langage. Cette notion de multitâche a été travaillée soigneusement par l’ISO. Notons que beaucoup de concepts ainsi que leurs constructions syntaxiques ont été introduits pour assurer un degré élevée de concurrence. Ceci permet une utilisation efficace du langage Ada dans la programmation des systèmes distribués et décentralisés.

Partie 2 : Développement des Outils pour