Exemples d'utilisation des tâches et objets protégés en ADA 95

© D. Mathieu     mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique
Créé le 13/12/1999 - Dernière mise à jour : 28/02/2000

Sommaire

Accès à une donnée en exclusion mutuelle

Type tâche - Discriminant - Identification - Attribut

Opérations en exclusion mutuelle grâce à un objet protégé

Exceptions et tâches

Initialisation d'une tâche par son discriminant

Serveur concurrent

Priorités

Problème des lecteurs-rédacteurs

 

Divers

Accès à une donnée en exclusion mutuelle

Mise en évidence de l'incohérence d'une variable globale utilisée par deux tâches

    Fichier Essai01.adb

Programme destiné à montrer que des commutations de tâches peuvent se produire à n'importe quel moment. Trois exécutions successives :
 
Valeur finale de Partage = 613651
Valeur finale de Partage = 696890
Valeur finale de Partage = 606336

    En fait, on ne sait pas à quel moment la tâche principale récupère la valeur de la variable Partage avant de l'afficher.Une solution est tentée avec Essai02.adb

    On ne voit rien jusqu'à  Maxi : constant := 100_000;

Remarques :

  1. en inversant l'ordre des tâches, les résultats sont négatifs,
  2. noter la définition de T_Entier,
  3. noter l'identificateur de tâche, construit avec le préfixe Tsk (Norme interne I.U.T. Aix ...).
Sommaire

Premier essai de résolution du problème

    Fichier Essai02.adb

    Essai de résolution du problème en effectuant l'initialisation de la variable Partage avant le lancement des deux tâches, et en affichant son contenu final après la fin des deux tâches.

    Programme destiné à montrer que des commutations de tâches peuvent se produire à n'importe quel moment. Il faut maintenant Maxi : constant := 10_000_000; pour voir apparaître le phénomène. Deux exécutions successives :
 
Valeur initiale de Partage = 0
Valeur finale   de Partage = -1360466

et
 
Valeur initiale de Partage = 0
Valeur finale   de Partage = 2080775

    Il apparaît donc que l'incohérence du résultat est exclusivement due au parallélisme des deux tâches : il doit arriver parfois qu'une tâche n'ait pas le temps de sauvegarder en mémoire le résultat d'un calcul avant que l'autre tâche n'entame elle-même une succession d'opérations. Toutes ses opération sont perdues lorsque la première tâche reprend la main et sauvegarde la dernière valeur qu'elle a calculée.

Sommaire

Sérialisation des tâches

    Fichier Essai03.adb

    Programme destiné à montrer que le problème soulevé dans les programmes Essai01 et Essai02 ne provient que du parallélisme : les deux tâches sont ici sérialisées (exécutées en série, c'est-à-dire l'une après l'autre). Résultat :
 
Valeur initiale de Partage = 0
Valeur finale   de Partage = 0

Sommaire

Encapsulation de la donnée partagée dans un paquetage

    Fichier Essai04.adb

    Programme destiné à montrer que l'encapsulation d'une donnée dans un paquetage (P_EntierCaché.ads  et  P_EntierCaché.adb) ne la met pas à l'abri des problèmes de partage d'accès :
 
Valeur initiale de Partage = 0
Valeur finale   de Partage = -1410043

    => l'accès à la donnée du paquetage N'EST PAS ATOMIQUE.

    Le paquetage P_EntierCaché offre seulement un accesseur Get et un modifieur Set, ainsi que les deux fonctions Incrementer et Decrementer. On peut donc espérer que la donnée soit protégée, mais ce n'est pas le cas. En fait, chaque tâche exécute successivement les instructions de ce paquetage mais peuvent être interrompues à tout moment, comme précédemment.

    Remarques :

  1. les tâches qui incrémentent et décrémentent sont transformées en types tâches. Cela permet de lancer simultanément plusieurs tâches de chacun de ces types. Noter la construction du type T_Entier. Il est alors nécessaire de modifier l'intervalle de variation des indices de boucle (1..Maxi et non 1..T_Entier'Last).
  2. en pratique, une seule tâche de chaque type suffit pour provoquer  l'incohérence de la valeur finale de Partage.
  3. dans le paquetage P_EntierCaché, si l'incrémentation et la décrémentation se bornent à modifier la valeur de I_Caché, je n'ai pas constaté d'incohérence finale même en lançant simultanément 200 tâches (l'exécution est assez longue !!!). J'ai donc allongé ces différentes opérations.
  4. noter l'identificateur de type tâche, construit avec le préfixe T_Tsk (autre norme interne I.U.T. Aix ...)
Sommaire

Type tâche - Discriminant - Identification - Attribut

Tâche avec discriminant, vecteur de tâches

    Fichier Essai05.adb

    Programme destiné à illustrer l'utilisation d'un type tâche avec discriminant. Un vecteur de deux tâches est créé avec le même discriminant => il est impossible de différencier les éditions en provenance de ces deux tâches. Affichage obtenu :
 
La tache  1 vous dit Coucou
La tache  2 vous dit Coucou
La tache  3 vous dit Coucou
La tache  3 vous dit Coucou
La tache  2 vous dit Coucou
La tache  3 vous dit Coucou
La tache  3 vous dit Coucou
La tache  3 vous dit Coucou
La tache  3 vous dit Coucou

    Remarques :

  1. noter l'utilisation d'une valeur par défaut pour le discriminant.
  2. il est impossible de créer un vecteur de tâches de discriminants différents.
  3. noter l'utilisation de delay suivi d'un flottant fixe de type Duration.
Sommaire

Article avec champ de type tâche

    Fichier Essai06.adb

    Programme illustrant la possibilité d'utiliser un champ de type tâche dans un article. Ce programme provoque l'affichage suivant (variable si plusieurs executions successives) :
 
La tache  2 vous dit Coucou
R2.I  = 3 La tache 
2 Tache cadre achevee vous dit Coucou

La tache  2 vous dit Coucou
La tache  2 vous dit Coucou

    Remarques :

  1. le type T_TskCoucou est un type non contraint (il n'y a pas de valeur par défaut) => il ne peut plus être utilisé tel quel.
  2. noter l'utilisation du discriminant d'un article pour instancier le discriminant d'une tâche incluse dans l'article ...
  3. le type tâche est un type limited => un article dont un champ est une tâche est d'un type limited  => affectation interdite.
  4. on remarque l'imbrication des affichages des différentes tâches qui se déroulent en parallèle
Sommaire

Identification d'une tâche

    Fichier Essai07I.adb

    Programme de test de l'identification d'une tâche.

    Ce programme provoque l'affichage suivant (par exemple) :
 
La tache coucou_0244D378 vous dit Coucou
La tache coucou_0244D378 vous dit Coucou
Je suis la tache cadre : main_task_024449B0
Ma tache fille est     : coucou_0244D378

La tache coucou_0250160 vous dit Coucou
La tache coucou_0250160 vous dit Coucou
Je suis la tache cadre : main_task_024449B0
Ma tache fille est     : coucou_0250160

    Remarques :

  1. noter l'utilisation des éléments du paquetage Ada.Tsk_Identification : les fonctions Current_Task et Image, le type Task_ID.
  2. noter aussi l'accès à une tâche par son identificateur.
  3. une tâche ne peut connaître directement que son propre identificateur. Elle ne peut commaître l'identificateur d'une autre tâche que si cette dernière le lui communique.
  4. Autant que possible, les affichages sont effectuées par un seul appel à la fonction Put_Line, par concaténation de chaînes plutôt que par appels successifs à Put et Put_Line. Nous sommes dans un contexte d'affichages concurrents, et nous pouvons espérer que Put_Line est ATOMIQUE (il faudrait le vérifier).
Sommaire

Attribut d'une tâche

    Fichier Essai07A.adb

    Programme illustrant l'utilisation de l'attribut d'une tâche : lors de l'élaboration d'une tâche (il s'agit içi de la tâche Coucou de type T_TskCoucou), celle-ci reçoit un attribut initialisé à une valeur initiale, elle-même fixée lors de l'instanciation du paquetage générique Ada.Task_Attributes. Au cours de l'exécution de cette tâche, son attribut peut être modifié par elle-même (voir --1) ou par une autre qui la connaît (voir --2). La valeur initiale est cependant conservée et peut être rechargée à tout moment par la procedure Reinitialize (voir --3).

    Ce programme provoque l'affichage suivant (chronogramme indispensable pour suivre):
 
La tache coucou_0245D378 d'attribut A vous dit Coucou
La tache coucou_0245D378 d'attribut A vous dit Coucou
La tache coucou_0245D378 d'attribut C vous dit Coucou

La tache coucou_02460160 d'attribut A vous dit Coucou
La tache coucou_02460160 d'attribut B vous dit Coucou
La tache coucou_02460160 d'attribut B vous dit Coucou

    Remarques :

  1. noter l'utilisation des sous-programmes Value ("accesseur de l'attribut"), Set_Value ("modifieur de l'attribut") et Reinitialize du paquetage Ada.Task_Attributes instancié par le type Character.
  2. noter l'utilisation de l'entry WhoAreYou de la tâche Coucou, qui permet à toute tâche cliente d'identifier la tâche Coucou et donc de la désigner dans les procédures Set_Value ou Reinitialize.
Sommaire

Identification d'une tâche exécutant un objet protégé

    Fichier Essai13.adb

    Ce programme illustre que ce sont bien les tâches appelant un point d'entrée d'un objet qui exécutent le code de l'objet et non une autre tâche.

Sommaire

Opérations en exclusion mutuelle grâce à un objet protégé

Utilisation d'un objet protégé pour réaliser des opérations en exclusion mutuelle

    Fichier Essai08P.adb

    Ce programme résout le problème envisagé plus haut en utilisant un objet protégé.Il utilise pour cela le paquetage P_ProtEntier (fichiers P_ProtEntier.ads  et  P_ProtEntier.adb) très ressemblant au paquetage P_EntierCaché étudié précédemment, mais qui marche !

    Il provoque l'affichage suivant (après quelques minutes)
 
Valeur initiale de Partage = 0
Valeur finale   de Partage = 0

Sommaire

Rendez-vous par objet protégé

    Fichier Essai09A.adb

    Programme de réalisation d'un rendez-vous par l'intermédiaire d'un objet protégé.

    Le paquetage P_Tsk_BAO offre les outils suivants :

    Ce programme provoque l'affichage suivant (par exemple) :
 
1.002000000 : La tache t_tsk_promeneur_02460150 arrive au Rendez-vous
1.502000000 : La tache t_tsk_promeneur_02462F28 arrive au Rendez-vous
2.003000000 : La tache t_tsk_promeneur_02465D00 arrive au Rendez-vous
2.504000000 : La tache t_tsk_promeneur_02468AF8 arrive au Rendez-vous
2.504000000 : La tache t_tsk_promeneur_02460150 repart
2.504000000 : La tache t_tsk_promeneur_02462F28 repart
2.504000000 : La tache t_tsk_promeneur_02465D00 repart
2.504000000 : La tache t_tsk_promeneur_02468AF8 repart
3.005000000 : La tache t_tsk_promeneur_0246B8D0 arrive au Rendez-vous
3.505000000 : La tache t_tsk_promeneur_0246E6A8 arrive au Rendez-vous
4.006000000 : La tache t_tsk_promeneur_02468FD0 arrive au Rendez-vous
4.507000000 : La tache t_tsk_promeneur_02469560 arrive au Rendez-vous
4.507000000 : La tache t_tsk_promeneur_0246B8D0 repart
4.507000000 : La tache t_tsk_promeneur_0246E6A8 repart
4.507000000 : La tache t_tsk_promeneur_02468FD0 repart
4.507000000 : La tache t_tsk_promeneur_02469560 repart
5.007000000 : La tache t_tsk_promeneur_02469AD8 arrive au Rendez-vous

Remarques :

  1. Les affichages sont effectués par l'intermédiaire d'une tâche serveur, afin de garantir l'accès en exclusion mutuelle à l'écran. Sans cela, rien ne garantirait que l'instruction New_Line fût exécutée immédiatement après l'instruction Put. Ici, le problème  pourrait être contourné en utilisant directement la procédure Put_Line, en espérant qu'elle est atomique...
  2. apparemment, les tâches sont débloquées dans l'ordre dans lequel elles ont été bloquées (FIFO). Ce n'est cependant pas une preuve certaine, car l'affichage est effectué par l'intermédiaire d'une tâche serveur, et l'ordre dans lequel elles s'y présentent n'est pas maîtrisable.
  3. on constate une dérive du temps. En effet, si on lance 15 tâches promeneurs, le dernier affichage est le suivant :

  4.  
    8.022000000 : La tache t_tsk_promeneur_024661B8 arrive au Rendez-vous

    alors que 14 * 0.5 + 1 = 8 secondes. Ce problème est traité dans le fichier Essai09B.adb.

Sommaire

Rendez-vous par objet protégé sans dérive de temps

    Fichier Essai09B.adb

    Programme de réalisation d'un rendez-vous par l'intermédiaire d'un objet protégé : voir fichier Essai09A.adb.
La dernière ligne affichée est :
 
8.002000000 : La tache t_tsk_promeneur_024661B8 arrive au Rendez-vous

et on constate que la dérive augmente jusqu'à 0.009 secondes, après quoi le nouveau temps est réajusté. Rappel : le "granule" de temps est garanti inférieur à 20 millièmes de seconde = Duration'Small.

Sommaire

Exceptions et tâches

Exceptions levées dans le corps d'une tâche fille

    Fichier Essai10A.adb

    Ce programme montre qu'une exception levée et interceptée à l'intérieur du corps d'une tâche fille n'affecte en rien ni le déroulement des autres tâches filles, ni celui de la tâche cadre.

Sommaire

Non propagation d'une exceptions à la tâche mère

    Fichier Essai10B.adb

    Ce programme montre qu'une exception levée et non interceptée à l'intérieur du corps d'une tâche fille NE SE PROPAGE PAS à la tâche cadre.

Sommaire

Exceptions levées au cours de l'élaboration d'une tâche

    Fichier Essai10C.adb

    Ce programme montre que, quel que soit le nombre de tâches filles dont l'élaboration échoue, UNE SEULE EXCEPTION Tasking_Error est reçue par la tâche cadre.Les autres tâches continuent normalement.

Sommaire

Exceptions levées au cours de l'élaboration d'une tâche créée dynamiquement

    dans le corps de la tâche cadre : fichier Essai10D.adb

    Ce  programme montre que la première exception levée DANS LE CORPS DE LA TACHE CADRE lors de l'élaboration d'une tâche fille achève la tâche cadre. Cependant, les autres tâches déjà élaborées et lancées continuent normalement. La tâche cadre ne se termine que lorsque toutes les tâches filles sont achevées.

    dans la partie déclarative de la tâche cadre : fichier Essai10E.adb

   Ce  programme montre que la première exception levée dans la partie déclarative de la  tâche cadre lors de l'élaboration d'une tâche fille achève la tâche cadre. L'exception TASKING_ERROR est levée dans la partie déclarative de la tâche cadre et ne peut donc pas être interceptée au niveau de son corps. Cependant, les autres tâches déjà élaborées et lancées continuent normalement. La tâche cadre ne se termine que lorsque toutes les tâches filles sont achevées.

Sommaire

Exceptions levées dans le corps d'une tâche créée dynamiquement dans la partie déclarative de sa tâche cadre

    Fichier Essai10F.adb

   Ce  programme montre que l'exception levée dans le corps de la classe fille, même créée dynamiquement dans la partie déclarative, est interceptée par le traitant d'exception de la tâche cadre.

Sommaire

Exceptions levées pendant un rendez-vous

    Fichier Essai17.adb

    Ce programme illustre l'arrêt d'une tâche pendant un rendez-vous. Dans la deuxième partie, l'exception levée dans le accept du serveur est interceptée par le serveur et le client

Sommaire

Initialisation d'une tâche par son discriminant

Initialisation directe par le discriminant

    Fichier Essai16A.adb

    Il s'agit de communiquer un délai à la tâche lors de son élaboration. Attention : le type Duration n'étant pas un type discret, il est  impossible d'initialiser directement le délai.

Initialisation par le discriminant servant d'indice dans un tableau de valeurs initiales

    Fichier Essai16B.adb

    Si plusieurs tâches semblables doivent recevoir des valeurs initiales distinctes, elles ne peuvent pas être créées dans un tableau. Elles doivent être créées dynamiquement, à chacune étant passé un indice pointant dans un vecteur de valeurs initiales. Cette solution est très inélégante.

Initialisation par le discriminant avec valeur par défaut (J. Barnes)

    Fichier Essai16C.adb

    Cette solution utilise un discriminant dont la valeur par défaut n'est pas une constante, mais le résultat d'une fonction évaluée lors de l'élaboration de la tâche, et qui renvoie un indice dans le tableau des valeurs initiales. Cette solution, due à J. Barnes, est plus élégante que la précédente.

Initialisation par le discriminant pointant sur un article

    Fichier Essai16D.adb

    Le programme utilise un pointeur d'article pour passer l'ensemble des paramètres à une tâche lors de son initialisation.

Sommaire

Serveur concurrent

    Fichier Essai14.adb

    Ce programme illustre l'utilisation d'un serveur concurrent. La notion de serveur concurrent est développée dans le cours correspondant. La déclaration et le corps de la tâche TskServeur sont développés dans les fichiers P_Serveur.ads et P_Serveur.adb.

Sommaire

Priorités

Tâche contrôleur à plusieurs points d'entrée

    Fichier Priorite01.adb

    Le chronogramme suivant est obtenu :
 
--     0---1---2---3---4---5---6---7---8---9--10--11
-- B1  ----ASSSSSSS--------------------------------
-- B2  ----A-------------------------------SSSSSSSS
-- N1  --------A-----------SSSSSSSS----------------
-- U1  --------A---SSSSSSSS------------------------
-- U2  ------------------------A---SSSSSSSS--------

Bx, Nx et Ux désignent les tâches clients de priorité Basse, Normale et Urgente, A leur arrivée et S lorsqu'elles sont servies.

Sommaire

Tâche contrôleur avec une famille d'entrées

    Fichier Priorite02.adb

    Le chronogramme suivant est obtenu :
 
--     0---1---2---3---4---5---6---7---8---9--10--11
-- B1  ----ASSSSSSS--------------------------------
-- B2  ----A-------------------------------SSSSSSSS
-- N1  --------A-----------SSSSSSSS----------------
-- U1  --------A---SSSSSSSS------------------------
-- U2  ------------------------A---SSSSSSSS--------

Sommaire

Tâche contrôleur à nombreux points d'entrée

    Fichier Priorite05.adb

    Lorsque le nombre de niveaux de priorité est important, l'instruction select... est remplacée par une boucle infinie.

    Le chronogramme suivant est obtenu :
 
--     0---1---2---3---4---5---6---7---8---9--10--11
-- B1  ----ASSSSSSS--------------------------------
-- B2  ----A-------------------------------SSSSSSSS
-- N1  --------A-----------SSSSSSSS----------------
-- U1  --------A---SSSSSSSS------------------------
-- U2  ------------------------A---SSSSSSSS--------

Sommaire

Tâche contrôleur à nombreux points d'entrée sans attente active

    Fichier Priorite06.adb

    J. Barnes propose une modification pour supprimer l'attente active.

    Le chronogramme suivant est obtenu :
 
--     0---1---2---3---4---5---6---7---8---9--10--11
-- B1  ----ASSSSSSS--------------------------------
-- B2  ----A-------------------------------SSSSSSSS
-- N1  --------A-----------SSSSSSSS----------------
-- U1  --------A---SSSSSSSS------------------------
-- U2  ------------------------A---SSSSSSSS--------

Sommaire

Objet protégé contrôleur avec une famille d'entrées (solution de J. Barnes)

    Fichier Priorite03.adb

    Ce programme ne marche pas car la barrière est toujours levée (les conditions sont toujours justes !?*)

Sommaire

Divers

Instanciation d'un paquetage générique par un point d'entrée d'une tâche

    Fichier Essai15.adb

    Ce programme illustre l'utilisation du point d'entrée d'une tâche comme paramètre de généricité d'un paquetage générique : le paquetage générique P_Tsk_Text_IO exporte la procédure Put_Line qui affiche une chaîne en la faisant précéder de identificateur de la tâche qui demande l'affichage. Pour réaliser cet affichage, elle utilise la procédure d'affichage qui lui est passée en paramètre effectif de généricité. Dans cet exemple, le premier affichage est réalisé par une tâche chargée de l'affichage.

Sommaire

Appel d'entrée conditionnel select ... or et select ... else

    Fichier Essai11.adb

    Ce  programme illustre les deux possibilités d'appel d'entrée conditionnel.

Sommaire

Points de synchronisation entre les tâches filles et leur tâche cadre

    Fichier Essai12.adb

    Ce programme illustre la notion de point de synchronisation entre les tâches filles et leur tâche cadre. En particulier dans le cas où une tâche est créée dynamiquement. Comme on le constate, la tâche cadre ne reprend que lorsque les deux tâches filles du premier bloc sont terminées, qu'elles soient activées dans la déclaration ou dans le corps du bloc. En revanche, la tâche cadre reprend aussitôt après le second bloc alors que trois tâches filles ont été élaborées et activées dans la déclaration ou le corps du second bloc.

    Cet exemple montre que la tâche cadre correspond A LA PORTEE DU TYPE POINTEUR et non à celle des pointeurs eux-mêmes ou du type tâche.

Sommaire

Appel d'un point d'entrée sans préfixe - Arrêt d'une tâche cadre par sa fille

    Fichier Chronometre.adb

   Le programme suivant montre

     La tâche TskChronometre renvoie le temps réel (en secondes) écoulé depuis son activation. Il est supposé que le service peut durer un temps quelconque et que le chronomètre doit alors rattraper le temps perdu. On rappelle que, si le temps est passé lors del'exécution de l'instruction delay until ..., celle-ci est non bloquante et la tâche continue immédiatement.

    Remarques :

  1. il y a bien entendu une façon plus simple d'obtenir le même résultat!
  2. la tâche TskTestChronometre devrait être supprimée en dehors de la phase de test. Comme elle introduit une durée du service, et que le profil de l'entrée GetTempsEcoulé ne doit pas être modifié, la tâche TskTestChronometre est interne à la tâche TskChronometre. Elle comporte le jeu d'essai
  3. l'exécution de ce programme provoque l'affichage suivant (chronogramme intéressant à faire!) :
-- Temps ecoule (en Secondes) :               1
-- Temps ecoule (en Secondes) :               5
-- Temps ecoule (en Secondes) :              11
-- Temps ecoule (en Secondes) :              15

Sommaire
 

© D. Mathieu     mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique