• Aucun résultat trouvé

Chapitre 3 : Programmes Ada Concurrents

5. Communication Entre Tâches

Normalement, il est nécessaire pour les tâches de communiquer entre eux; par exemple, une tâche dans

spreadsheet cell a besoin d'être demandée pour connaître la valeur de cell de temps en temps, ou spreadsheet peut avoir besoin d'être demandée pour se recalculer. Dans de tels cas, la spécification de tâche a besoin d'être étendue pour mentionner les services qu'une tâche peut fournir. Dans ce qui suit, une spécification d'une tâche

spreadsheet qui permet à d'autres tâches de demander d'elle son recalcul : task type Spreadsheet_Task is

entry Recalculate; end Spreadsheet_Task;

Sheet : Spreadsheet_Task; -- declare a Spreadsheet_Task

Ce type de tâche fournit une specification d'entrée (entry) qu'une autre tâche peut appeler comme une procédure. Par exemple, une tâche peut demander à Sheet de recalculer avec un appel d'entrée comme le suivant :

Sheet.Recalculate;

Le corps de tâche doit fournir une manière pour servir les appels à ses entrées. Ceci peut être fait en utilisant une instruction accept :

task body Spreadsheet_Task is begin

end Spreadsheet_Task;

Quand le corps de tâche commence à s'exécuter, il attendra au niveau de l’instruction accept jusqu'à ce qu'un appel sur Recalculate soit fait. Il appellera alors une procédure Do_Recalculation pour exécuter le recalcul et pour aller autour de la boucle encore attendre le prochain appel de Recalculate. Si une autre tâche appelle

Recalculate avant que la tâche spreadsheet revienne de nouveau à l’instruction accept, la tâche appelante est forcée d'attendre. Ainsi la tâche appelante et celle appelée attendront l'une l'autre jusqu'à ce qu'elles soient les deux prêtes. Ceci est réalisée quand l’appelante attend son appel d'entrée d’être accepté et l'appelante attend au niveau de l’instruction accept. Cette synchronisation des deux tâches est connue sous le nom de rendez-vous. Le corps de tâche décrit ci-dessus a un problème principal ; puisque c'est une boucle infinie il n'y a aucune manière de l’arrêter, ainsi la tâche maître ne pourra pas se terminer non plus ; il va attendre infiniment à la fin du bloc où la tâche spreadsheet a été déclaré. Une solution consiste à arrêter (abort) en utilisant l’instruction abort: abort Sheet;

Ceci forcera la tâche et toutes les tâches dépendantes d’elle de se terminer. Cependant, la tâche spreadsheet pourrait être à mi-chemin d’un recalcul pendant ce temps là. Une meilleure manière serait d'ajouter une autre entrée pour permettre la tâche maître de lui demander de s'arrêter d'une façon ordonnée :

task type Spreadsheet_Task is entry Recalculate;

entry Shutdown; end Spreadsheet_Task;

Maintenant le corps de tâche doit pouvoir répondre aux appels à n’importe quelle de ses entrées. Ce n'est pas bien de les accepter l’un après l’autre dans une boucle puisque ceci forcera les entrées à être appelées alternativement. Une solution serait d'examiner si des appels sont en suspension avant de les accepter. Nous pouvons faire ceci en utilsant l’attribut de compte ('Count attribute) pour une entrée qui donne le nombre d'appels en suspension pour cette entrée :

task body Spreadsheet_Task is begin

loop

if Recalculate'Count > 0 then

accept Recalculate; Do_Recalculation; elsif Shutdown'Count > 0 then

accept Shutdown; exit; end if;

end loop;

end Spreadsheet_Task;

Cependant, ce n'est pas particulièrement fiable. Car nous verrons plus tard, les tâches peuvent choisir d'abandonner des appels d'entrée s'elles ne sont pas répondues dans une certaine période de temps, et ceci

signifie que même si Recalculate'Count est différent de zéro, avant que nous exécutons l’instruction accept pour Recalculate, la tâche appelante pourrait avoir chronométré dehors et abandonné son appel, dans ce cas nous serons coincé à l’instruction accept jusqu'à ce qu'une autre tâche appele Recalculate. Et si cela ne se produit jamais, nous ne pourons jamais accepter un appel à Shutdown. La solution correcte à ceci est de mettre les appels à l'intérieur de l’instruction select :

task body Spreadsheet_Task is begin

loop select

accept Recalculate; Do_Recalculation; or

accept Shutdown; exit; end select;

end loop;

end Spreadsheet_Task;

Cette instruction select contient deux alternatives accept qui doivent chacune être dirigées (headed) par une instruction accept. Elle attend jusqu'à ce qu'une des entrées décrites dans les instructions accept soit appelée, et elle éxécute alors l'alternative appropriée. Si les appels aux deux entrées sont déjà en suspension, un sera accepté d’une manière non-déterministe. L’instruction select s’achève après que l'alternative choisie a été exécutée; si

Recalculate est appelé, la tâche revient au début de la boucle et attend un autre appel d'entrée, mais si

Shutdown est appelé elle sort de la boucle et se termine. Il faut noter que la tâche ne répond pas aux appels à

Shutdown si un Recalculate est en évolution; il répond seulement quand il attend un appel d'entrée au niveau de l’instruction select, qui s'avérera justement après la fin de l'appel de Recalculate et le retour au début de la boucle. Cette solution exige que la tâche maître appele Shutdown explicitement quand elle veut terminer la tâche. L'inconvénient de cette approche est qu'il est possible d'oublier d'appeler Shutdown. Une meilleure solution est d'ajouter une alternative de terminaison à l’instruction select :

task body Spreadsheet_Task is begin

loop select

accept Recalculate; Do_Recalculation; or

accept Shutdown; exit; or

terminate; end select; end loop;

L'alternative de terminaison doit être la dernière dans une instruction select, et elle ne peut contenir qu'une instruction terminate comme celle qui est montrée ci-dessus. Quand la tâche maître arrive à la fin du bloc où

spreadsheet a été déclaré, la tâche spreadsheet se terminera à la prochaine fois quand l’instruction select est exécutée (ou immédiatement, si la tâche attend déjà dans l’instruction select). Ceci signifie que le maître ne doit rien faire pour provoquer la terminaison de la tâche, mais il peut encore appeler Shutdown s'il veut terminer la tâche avant d’atteindre la fin du bloc où elle a été déclarée. Notons qu'une fois qu'une tâche s'est terminée, nous serons récompensés par une exception de Tasking_Error si nous essayons d'appeler une de ses entrées. Les instructions select peuvent également être utilisées pour des appels d'entrée. Si une tâche appelée n’est pas prête pour attendre un rendez-vous, elle peut utiliser une instruction select avec une alternative else comme ceci : select

Sheet.Recalculate; else

Put_Line ("Sheet is busy -- giving up"); end select;

Dans ce cas, si Sheet ne peut pas accepter l'appel d'entrée pour Recalculate immédiatement, l'appel d'entrée sera abandonné et l'alternative else sera exécutée. Si la tâche appelée est disposée à attendre un temps limité, on peut utiliser une instruction select avec une alternative delay comme ceci :

select

Sheet.Recalculate; or

delay 5.0;

Put_Line ("Sheet has been busy for 5 seconds -- giving up"); end select;

Si l'appel d'entrée n'est pas accepté dans le temps indiqué au niveau de l’instruction delay (cinq secondes dans ce cas), il est abandonné et c’est l'alternative delay qui sera exécutée. Une instruction delay until peut toujours être utilisée au lieu d'une instruction delay :

select

Sheet.Recalculate; or

delay until Christmas; Put_Line ("Sheet has been busy for ages -- giving up"); end select;

Nous pouvons également fixer une limite supérieure sur le temps qu’elle prend pour traiter un appel d'entrée : select

delay 5.0; Put_Line ("Sheet not recalculated yet -- recalculation abandoned"); then abort

Sheet.Recalculate; end select;

Ceci commence à évaluer les instructions entre then abort et end select, qui est dans ce cas un appel à

Sheet.Recalculate. Si le délai indiqué par l’instruction delay (ou delay until) après select expire avant que ceci se termine, l'appel à Sheet.Recalculate est abondonné (comme par une instruction abort) et le message ‘Sheet

not recalculated yet -- recalculation abandoned’ sera affiché. L’utilisateur n’est pas limité à utiliser ceci en liaison avec le traitement multitâche; par exemple, nous pourions l'utiliser pour abondonner des calculs prolongés où le temps total d'exécution est important (ou où le calcul pourrait diverger pour donner des temps d'exécution potentiellement infinis) :

select

delay 5.0; Put_Line ("Horribly long calculation abandoned"); then abort

Horribly_Long_And_Possibly_Divergent_Calculation; end select;

Une instruction select à l'intérieur d'un corps de tâche, peut également faire une alternative else ou une ou plusieurs alternatives delay au lieu d'une alternative terminate. Nous devons également avoir au moins une alternative accept. Une alternative else est activée si aucune des instructions accept n'a d’un appel en attente d'entrée ; une alternative delay est activée si aucune des instructions accept n'accepte un appel d'entrée dans le temps indiqué dans l’instruction delay. Ces trois possibilités (else, delay et terminate) sont mutuellement exclusives. Nous ne pouvons pas avoir une alternative de delay en même temps qu’une alternative terminate, par exemple.