Section courante

A propos

Section administrative du site

 Langage  Elément  Tutoriel  Programmation  IDE  Annexe  Aide 
ABAP/4
Ada
Assembleur
Assembly & bytecode
ASP (Active Server Pages)
Basic
C
C++
C# (C Sharp)
Cobol
ColdFusion
Fortran
HTML
Java
JavaScript
LISP
Logo
LotusScript
Oberon
Pascal
Perl
PHP
PL/1
Prolog
Python
Rebol
REXX
Ruby
Rust
SAS
NoSQL
SQL
Swift
X++ (Axapta)
GNAT
SMALLAda
VHDL
Assembleur 370
Assembleur 1802
Assembleur 4004
Assembleur 6502
Assembleur 6800
Assembleur 68000
Assembleur 8080 et 8085
Assembleur 8089
Assembleur 80x86
Assembleur AGC4
Assembleur ARM
Assembleur DPS 8000
Assembleur i860
Assembleur Itanium
Assembleur MIPS
Assembleur PDP-11
Assembleur PowerPC
Assembleur RISC-V
Assembleur SPARC
Assembleur SuperH
Assembleur UNIVAC I
Assembleur VAX
Assembleur Z80
Assembleur Z8000
Assembleur z/Architecture
ASSEMBLER/MONITOR 64
Micol Assembler
GFA Assembler
A86
MASM (Macro Assembler)
TASM (Turbo Assembler)
CIL
Jasmin
LLVM
MSIL
Parrot
P-Code (PCode)
SWEET16
G-Pascal
ASP 1.0
ASP 2.0
ASP 3.0
ASP.NET
ASP.NET Core
ABasiC (Amiga)
Adam SmartBASIC
Altair BASIC
AmigaBASIC (Amiga)
AMOS Basic (Amiga)
Atari Basic (Atari 400, 600 XL, 800, 800XL)
Basic Apple II (Integer BASIC/APPLESOFT)
Basic Commodore 64 (CBM-BASIC)
Basic Commodore 128 (BASIC 7.0)
Basic Commodore VIC-20 (CBM-BASIC 2.0)
Basic Coco 1 (Color Basic)
Basic Coco 2 (Extended Color Basic)
Basic Coco 3 (Extended Color Basic 2.0)
BASICA (PC DOS)
Basic Pro
BBC BASIC
Blitz BASIC (Amiga)
DarkBASIC
Dartmouth BASIC
GFA-Basic (Atari ST/Amiga)
GWBASIC (MS-DOS)
Liberty BASIC
Locomotive BASIC (Amstrad CPC)
MSX-Basic
Omikron Basic (Atari ST)
Oric Extended Basic
Power Basic
Quick Basic/QBasic (MS-DOS)
Sinclair BASIC (ZX80, ZX81, ZX Spectrum)
ST BASIC (Atari ST)
Turbo Basic
Vintage BASIC
VBScript
Visual Basic (VB)
Visual Basic .NET (VB .NET)
Visual Basic pour DOS
Yabasic
BeckerBASIC
SIMONS' BASIC
Basic09 d'OS-9
Disk Extended Color Basic
Basic09 d'OS-9
Disk Extended Color Basic
Access
Excel
Visual Basic pour Windows
Visual Basic .NET pour Windows
C Shell Unix (csh)
C pour Amiga
C pour Atari ST
C pour DOS
C pour Falcon030
C pour GEMDOS (Atari ST)
C pour Linux
C pour PowerTV OS
C pour OS/2
C pour Unix
C pour Windows
Aztec C
CoCo-C
GNU C
HiSoft C
IBM C/2
Introl-C
Lattice C
Microsoft C
MinGW C
MSX-C
Open Watcom C
OS-9 C Compiler
Pure C
Quick C
Turbo C
HiSoft C for Atari ST
HiSoft C for CP/M (Amstrad CPC)
C++ pour OS/2
C++ pour Windows
Borland C++
C++Builder
IBM VisualAge C++
Intel C++
MinGW C++
Open Watcom C++
Symantec C++
Turbo C++
Visual C++
Visual C++ .NET
Watcom C++
Zortech C++
C# (C Sharp) pour Windows
Apple III Cobol
Microsoft Cobol
BlueDragon
Lucee
OpenBD
Railo
Smith Project
Microsoft Fortran
WATFOR-77
CSS
FBML
Open Graph
SVG
XML
XSL/XSLT
LESS
SASS
GCJ (GNU)
JSP
Jython
Visual J++
Node.js
TypeScript
AutoLISP
ACSLogo
LotusScript pour Windows
Amiga Oberon
Oberon .NET
Apple Pascal
Delphi/Kylix/Lazarus
Free Pascal
GNU Pascal
HighSpeed Pascal
IBM Personal Computer Pascal
Lisa Pascal
Maxon Pascal
MPW Pascal
OS-9 Pascal
OSS Personal Pascal
Pascal-86
Pascal du Cray Research
Pascal/VS
Pascal-XT
PURE Pascal
QuickPascal
RemObjets Chrome
Sun Pascal
THINK Pascal
Tiny Pascal (TRS-80)
Turbo Pascal
UCSD Pascal
VAX Pascal
Virtual Pascal
Turbo Pascal for CP/M-80
Turbo Pascal for DOS
Turbo Pascal for Macintosh
Turbo Pascal for Windows
CodeIgniter (Cadre d'application)
Drupal (Projet)
Joomla! (Projet)
Phalanger (PHP .NET)
phpBB (Projet)
Smarty (balise)
Twig (balise)
Symfony (Cadre d'application)
WordPress (Projet)
Zend (Cadre d'application)
PL360
PL/M-80
PL/M-86
Turbo Prolog
CPython
IronPython
Jython
PyPy
AREXX
Regina REXX
JMP
Btrieve
Cassandra
Clipper
CouchDB
dBASE
Hbase
Hypertable
MongoDB
Redis
Access
BigQuery
DB2
H2
Interbase
MySQL
Oracle
PostgreSQL
SAP HANA
SQL Server
Sybase
U-SQL
Introduction
Les remarques
Les opérateurs
Les instruction conditionnelles
Les instructions de boucles
Type de données
Définition de fonction
Référence de mots réservés (mots clefs)
Référence de procédures et fonctions
Les modules (Packages)
Les premiers pas
Les éléments lexicaux
Les déclarations et types
Les noms et les expressions
Les instructions
Les sous-programmes
Les paquets
Les règles de visibilité
Les tâches
Structure du programme et problèmes de compilation
Les exceptions
Les unités génériques
Clauses de représentation et fonctionnalités dépendantes de la mise en oeuvre
Les entrées-sorties
Bonjour
Biochimie
Géographie
Géométrie
Histoire
Mathématique
Médicale
Météorologie
Océanographie
Sport
Temps
Trigonométrie
Validation
Calcul du calcium corrigé
Distance en Km entre deux longitudes et latitudes
Aire d'un cercle
Aire d'une surface de prisme rectangulaire
Aire d'un triangle
Distance entre deux points
Chiffre romain
Ackermann
Exp
Factoriel
Fibonacci
Log
Nombre premier
Odd
Random
Sqrt
Triangle Pascal
Hauteur utérine
Unité de mesure
Fréquence des vagues
Hockey
Année bissextile
Date de la Pâque
Atn/ATan/ArcTan
Courriel
AdaGIDE
GNAT Programming Studio
SET's Editor
Attributs de langages prédéfinis
Les pragma de langage prédéfinis
Environnement de langage prédéfini
Référence de termes et du vocabulaire
Téléchargement
Préface
Notes légal
Dictionnaire
Recherche

Les tâches

L'exécution d'un programme ne contenant pas de tâche est définie en termes d'exécution séquentielle de ses actions, selon les règles décrites dans d'autres pages. Ces actions peuvent être considérées comme exécutées par un seul processeur logique.

Les tâches sont des entités dont les exécutions se déroulent en parallèle dans le sens suivant. Chaque tâche peut être considérée comme exécutée par un processeur logique lui étant propre. Différentes tâches (différents processeurs logiques) se déroulent indépendamment, sauf aux points où elles se synchronisent.

Certaines tâches ont des entrées. Une entrée d'une tâche peut être appelée par d'autres tâches. Une tâche accepte un appel d'une de ses entrées en exécutant une instruction accept pour l'entrée. La synchronisation est réalisée par rendez-vous entre une tâche émettant un appel d'entrée et une tâche acceptant l'appel. Certaines entrées ont des paramètres ; les appels d'entrée et les instructions accept pour ces entrées sont les principaux moyens de communication de valeurs entre les tâches.

Les propriétés de chaque tâche sont définies par une unité de tâche correspondante se composant d'une spécification de tâche et d'un corps de tâche. Les unités de tâche sont l'une des quatre formes d'unité de programme dont les programmes peuvent être composés. Les autres formes sont les sous-programmes, les paquets et les unités génériques. Les propriétés des unités de tâches, des tâches et des entrées, ainsi que les instructions affectant l'interaction entre les tâches (c'est-à-dire les instructions d'appel d'entrée, les instructions d'acceptation, les instructions de délai, les instructions de sélection et les instructions d'abandon) sont décrites dans cette page.

Remarque : les tâches parallèles (processeurs logiques parallèles) peuvent être implémentées sur des multi-ordinateurs, des multiprocesseurs ou avec une exécution entrelacée sur un seul processeur physique. D'autre part, chaque fois qu'une implémentation peut détecter que le même effet peut être garanti si des parties des actions d'une tâche donnée sont exécutées par différents processeurs physiques agissant en parallèle, elle peut choisir de les exécuter de cette manière; dans un tel cas, plusieurs processeurs physiques implémentent un seul processeur logique.

Spécifications de tâches et corps de tâches

Une unité de tâche se compose d'une spécification de tâche et d'un corps de tâche. Une spécification de tâche commençant par les mots réservés type de tâche déclare un type de tâche. La valeur d'un objet d'un type de tâche désigne une tâche ayant les entrées, le cas échéant, étant déclarées dans la spécification de tâche; ces entrées sont également appelées entrées de cet objet. L'exécution de la tâche est définie par le corps de tâche correspondant.

Une spécification de tâche sans le mot réservé type définit une tâche unique. Une déclaration de tâche avec cette forme de spécification équivaut à la déclaration d'un type de tâche anonyme immédiatement suivie de la déclaration d'un objet du type de tâche, et l'identifiant de l'unité de tâche nomme l'objet. Dans la suite de cette page, les explications sont données en termes de déclarations de type de tâche; les explications correspondantes pour les déclarations de tâche unique découlent de l'équivalence indiquée :

declaration_de_tache ::= specification_de_tache;

specification_de_tache ::=
  task [type] identifiant [is
    [declaration_d_entree]
    [clause_de_representation]
  end [tache_simple_nom]]

tache_corps ::=
  task body tache_simple_nom is
       [ partie_declarative]
  begin
       sequence_d_instructions
  [ exception
       gestionnaire_d_exceptions
     | gestionnaire_d_exceptions]]
  end [tache_simple_nom];

Le nom simple au début d'un corps de tâche doit répéter l'identifiant de l'unité de tâche. De même, si un nom simple apparaît à la fin de la spécification ou du corps de tâche, il doit répéter l'identifiant de l'unité de tâche. Dans un corps de tâche, le nom de l'unité de tâche correspondante peut également être utilisé pour faire référence à l'objet tâche qui désigne la tâche qui exécute actuellement le corps; de plus, l'utilisation de ce nom comme marque de type n'est pas autorisée dans l'unité de tâche elle-même.

Pour l'élaboration d'une spécification de tâche, les déclarations d'entrée et les clauses de représentation, le cas échéant, sont élaborées dans l'ordre indiqué. De telles clauses de représentation ne s'appliquent qu'aux entrées déclarées dans la spécification de tâche.

L'élaboration d'un corps de tâche n'a d'autre effet que d'établir que le corps peut désormais être utilisé pour l'exécution de tâches désignées par des objets du type de tâche correspondant. L'exécution d'un corps de tâche est invoquée par l'activation d'un objet tâche du type correspondant. Les gestionnaires d'exceptions facultatifs à la fin d'un corps de tâche gèrent les exceptions levées pendant l'exécution de la séquence d'instructions du corps de tâche.

Exemples de spécifications de types de tâches :

  1. task type RESOURCE is
  2.   entry SEIZE;
  3.   entry RELEASE;
  4. end RESOURCE;
  5.  
  6. task type KEYBOARD_DRIVER is
  7.   entry READ  (C : out CHARACTER);
  8.   entry WRITE (C : in  CHARACTER);
  9. end KEYBOARD_DRIVER;

Exemples de spécifications de tâches individuelles :

  1. task PRODUCER_CONSUMER is
  2.   entry READ (V : out ITEM);
  3.   entry WRITE (E : in ITEM);
  4. end;
  5.  
  6. task CONTROLLER is
  7.   entry REQUEST(LEVEL)(D : ITEM); -- une famille d'entrées
  8. end CONTROLLER;
  9.  
  10. task USER;                        -- n'a aucune entrée

Exemple de tâches des charges et de corps correspondant :

  1. task PROTECTED_ARRAY is
  2.   -- INDEX et ITEM sont des types globaux
  3.    entry READ (N : in INDEX; V : out ITEM);
  4.    entry WRITE (N : in INDEX; E : in ITEM);
  5. end;
  6.  
  7. task body PROTECTED_ARRAY is
  8.   TABLE : array(INDEX) of ITEM := (INDEX => NULL_ITEM);
  9. begin
  10.   loop
  11.     select
  12.       accept READ (N : in INDEX; V : out ITEM) do
  13.         V := TABLE(N);
  14.       end READ;
  15.     or
  16.       accept WRITE(N : in INDEX; E : in ITEM) do
  17.         TABLE(N) := E;
  18.       end WRITE;
  19.     end select;
  20.   end loop;
  21. end PROTECTED_ARRAY;

Remarque : Une spécification de tâche spécifie l'interface des tâches du type de tâche avec d'autres tâches du même type ou de types différents, ainsi qu'avec le programme principal.

Types de tâches et objets de tâches

Un type de tâche est un type limité. Par conséquent, ni l'affectation ni la comparaison prédéfinie pour l'égalité et l'inégalité ne sont définies pour les objets de type tâche; de plus, le mode out n'est pas autorisé pour un paramètre formel dont le type est un type tâche.

Un objet tâche est un objet dont le type est un type de tâche. La valeur d'un objet tâche désigne une tâche possédant les entrées du type de tâche correspondant et dont l'exécution est spécifiée par le corps de tâche correspondant. Si un objet tâche est l'objet, ou un sous-composant de l'objet, déclaré par une déclaration d'objet, alors la valeur de l'objet tâche est définie par l'élaboration de la déclaration d'objet. Si un objet tâche est l'objet, ou une sous-composante de l'objet, créé par l'évaluation d'un allocateur, alors la valeur de l'objet tâche est définie par l'évaluation de l'allocateur. Pour tous les modes de paramètres, si un paramètre réel désigne une tâche, le paramètre formel associé désigne la même tâche; il en va de même pour un sous-composante d'un paramètre réel et le sous-composante correspondant du paramètre formel associé; enfin, il en va de même pour les paramètres génériques.

Exemples :

  1. CONTROL  : RESOURCE;
  2. TELETYPE : KEYBOARD_DRIVER;
  3. POOL     : array(1 .. 10) of KEYBOARD_DRIVER;
  4. -- voir aussi des exemples de déclarations de tâches uniques

Exemple de type d'accès désignant des objets de tâche :

  1. type KEYBOARD is access KEYBOARD_DRIVER;
  2.  
  3. TERMINAL : KEYBOARD := new KEYBOARD_DRIVER;

Remarques : Étant donné qu'un type de tâche est un type limité, il peut apparaître comme la définition d'un type privé limité dans une partie privée, et comme un paramètre réel générique associé à un paramètre formel dont le type est un type limité. En revanche, le type d'un paramètre formel générique de mode in ne doit pas être un type limité et ne peut donc pas être un type de tâche.

Les objets tâche se comportent comme des constantes (un objet tâche désigne toujours la même tâche) puisque leurs valeurs sont définies implicitement soit à la déclaration ou à l'allocation, soit par une association de paramètres, et puisqu'aucune affectation n'est disponible. Cependant, le mot réservé constant n'est pas autorisé dans la déclaration d'un objet tâche car cela nécessiterait une initialisation explicite. Un objet tâche étant un paramètre formel de mode in est une constante (comme tout paramètre formel de ce mode).

Si une application doit stocker et échanger des identités de tâches, elle peut le faire en définissant un type d'accès désignant les objets tâche correspondants et en utilisant des valeurs d'accès à des fins d'identification (voir l'exemple ci-dessus). L'affectation est disponible pour un tel type d'accès comme pour tout type d'accès.

Les déclarations de sous-types sont autorisées pour les types de tâches comme pour les autres types, mais il n'y a aucune contrainte applicable aux types de tâches.

Exécution de tâche - Activation de tâche

Un corps de tâche définit l'exécution de toute tâche désignée par un objet tâche du type de tâche correspondant. La partie initiale de cette exécution est appelée activation de l'objet tâche, ainsi que celle de la tâche désignée; elle consiste en l'élaboration de la partie déclarative, le cas échéant, du corps de tâche. L'exécution de différentes tâches, en particulier leur activation, se déroule en parallèle. Si une déclaration d'objet déclarant un objet tâche se produit immédiatement dans une partie déclarative, alors l'activation de l'objet tâche commence après l'élaboration de la partie déclarative (c'est-à-dire après avoir passé le mot réservé begin après la partie déclarative); de même, si une telle déclaration se produit immédiatement dans une spécification de paquet, l'activation commence après l'élaboration de la partie déclarative du corps de paquet. Il en va de même pour l'activation d'un objet tâche étant une sous-composante d'un objet déclaré immédiatement dans une partie déclarative ou une spécification de paquet. La première instruction suivant la partie déclarative n'est exécutée qu'après la conclusion de l'activation de ces objets tâche.

Si une exception est levée par l'activation d'une de ces tâches, cette tâche devient une tâche terminée; les autres tâches ne sont pas directement affectées. Si l'une de ces tâches devient ainsi terminée pendant son activation, l'exception TASKING_ERROR est levée à la fin de l'activation de toutes ces tâches (avec succès ou non) ; l'exception est levée à un endroit qui se trouve immédiatement avant la première instruction suivant la partie déclarative (immédiatement après le mot réservé begin). Si plusieurs de ces tâches deviennent ainsi terminées pendant leur activation, l'exception TASKING_ERROR n'est levée qu'une seule fois.

Si une exception est levée par l'élaboration d'une partie déclarative ou d'une spécification de paquet, alors toute tâche créée (directement ou indirectement) par cette élaboration et qui n'est pas encore activée devient terminée et n'est donc jamais activée.

Pour les règles ci-dessus, dans tout corps de paquet sans instructions, une instruction null est supposée. Pour tout paquet sans corps de paquet, un corps de paquet implicite contenant une seule instruction null est supposé. Si un paquet sans corps de paquet est déclaré immédiatement dans une unité de programme ou une instruction de bloc, le corps de paquet implicite apparaît à la fin de la partie déclarative de l'unité de programme ou de l'instruction de bloc; s'il existe plusieurs paquets de ce type, l'ordre des corps de paquet implicites n'est pas défini.

Un objet tâche étant l'objet, ou un sous-composante de l'objet, créé par l'évaluation d'un allocateur est activé par cette évaluation. L'activation commence après toute initialisation de l'objet créé par l'allocateur ; si plusieurs sous-composantes sont des objets tâche, ils sont activés en parallèle. La valeur d'accès désignant un tel objet n'est renvoyée par l'allocateur qu'après la conclusion de ces activations.

Si une exception est levée par l'activation de l'une de ces tâches, cette tâche devient une tâche terminée ; les autres tâches ne sont pas directement affectées. Si l'une de ces tâches devient ainsi terminée pendant son activation, l'exception TASKING_ERROR est levée à la fin de l'activation de toutes ces tâches (avec succès ou non); l'exception est levée à l'endroit où l'allocateur est évalué. Si plusieurs de ces tâches sont ainsi terminées lors de leur activation, l'exception TASKING_ERROR n'est levée qu'une seule fois.

Si une exception est levée par l'initialisation de l'objet créé par un allocateur (donc avant le début de toute activation), toute tâche désignée par une sous-composante de cet objet devient terminée et n'est donc jamais activée.

Exemple :

  1. procedure P is
  2.  A, B : RESOURCE;    -- élaborer les objets de tâche A, B
  3.  C    : RESOURCE;    -- élaborer l'objet de tâche C
  4. begin
  5.  -- les tâches A, B, C sont activées en parallèle avant la première instruction
  6.  ...
  7. end

Remarques : Une entrée d'une tâche peut être appelée avant que la tâche n'ait été activée. Si plusieurs tâches sont activées en parallèle, l'exécution de l'une de ces tâches n'a pas besoin d'attendre la fin de l'activation des autres tâches. Une tâche peut être terminée pendant son activation soit à cause d'une exception, soit parce qu'elle est interrompue.

Dépendance des tâches - Fin des tâches

Chaque tâche dépend d'au moins un maître. Un maître est une construction étant soit une tâche, soit une instruction de bloc ou un sous-programme en cours d'exécution, soit un paquet de bibliothèque (un paquet déclaré dans une autre unité de programme n'est pas un maître). La dépendance à un maître est une dépendance directe dans les deux cas suivants :

  1. La tâche désignée par un objet tâche étant l'objet, ou un sous-composante de l'objet, créé par l'évaluation d'un allocateur dépend du maître élaborant la définition de type d'accès correspondante.
  2. La tâche désignée par tout autre objet tâche dépend du maître dont l'exécution crée l'objet tâche.

De plus, si une tâche dépend d'un maître donné qui est une instruction de bloc exécutée par un autre maître, alors la tâche dépend aussi de cet autre maître, de manière indirecte; il en va de même si le maître donné est un sous-programme appelé par un autre maître, et si le maître donné est une tâche dépendant (directement ou indirectement) d'un autre maître. Des dépendances existent pour les objets d'un type privé dont la déclaration complète est en termes de type de tâche.

On dit qu'une tâche a terminé son exécution lorsqu'elle a terminé l'exécution de la séquence d'instructions qui apparaît après le mot réservé begin dans le corps correspondant. De même, on dit qu'un bloc ou un sous-programme a terminé son exécution lorsqu'il a terminé l'exécution de la séquence d'instructions correspondante. Pour une instruction de bloc, l'exécution est également dite terminée lorsqu'elle atteint une instruction exit, return ou goto transférant le contrôle hors du bloc. Pour une procédure, l'exécution est également dite terminée lorsqu'une instruction return correspondante est atteinte. Pour une fonction, l'exécution est également dite terminée après l'évaluation de l'expression de résultat d'une instruction return. Enfin, l'exécution d'une tâche, d'une instruction de bloc ou d'un sous-programme est terminée si une exception est levée par l'exécution de sa séquence d'instructions et qu'il n'y a pas de gestionnaire correspondant, ou, s'il y en a un, lorsqu'elle a terminé l'exécution du gestionnaire correspondant.

Si une tâche n'a pas de tâche dépendante, sa terminaison a lieu lorsque son exécution est terminée. Après sa terminaison, une tâche est dite terminée. Si une tâche a des tâches dépendantes, sa terminaison a lieu lorsque l'exécution de la tâche est terminée et que toutes les tâches dépendantes sont terminées. Une instruction de bloc ou un corps de sous-programme dont l'exécution est terminée n'est pas quitté tant que toutes ses tâches dépendantes ne sont pas terminées.

La terminaison d'une tâche a lieu si et seulement si son exécution a atteint une alternative de terminaison ouverte dans une instruction select, et les conditions suivantes sont satisfaites :

Lorsque les deux conditions sont remplies, la tâche considérée devient terminée, ainsi que toutes les tâches qui dépendent du maître considéré.

Exemple :

  1. declare
  2.   type GLOBAL is access RESOURCE;
  3.   A, B : RESOURCE;
  4.   G : GLOBAL;
  5. begin
  6.  -- activation of A and B
  7.  declare
  8.   type LOCAL is access RESOURCE;
  9.    X : GLOBAL := new RESOURCE;   -- activation de X.all
  10.    L : LOCAL := new RESOURCE;    -- activation de L.all
  11.    C : RESOURCE;
  12. begin
  13.  activation of C
  14.   G := X;                        -- G et X désignent tous deux le même objet de tâche
  15.  end;                            -- attendre la fin de C et L.all (mais pas X.all)
  16. end;                             -- attendre la fin de A, B et G.all

Remarques : Les règles données pour la terminaison impliquent que toutes les tâches dépendant (directement ou indirectement) d'un maître donné et n'étant pas déjà terminées, peuvent être terminées (collectivement) si et seulement si chacune d'entre elles attend une alternative de terminaison ouverte d'une instruction select et que l'exécution du maître donné est terminée.

Les règles habituelles s'appliquent au programme principal. Par conséquent, la terminaison du programme principal attend la terminaison de toute tâche dépendante même si le type de tâche correspondant est déclaré dans un paquetage de bibliothèque. D'autre part, la terminaison du programme principal n'attend pas la terminaison des tâches dépendant des paquets de bibliothèque; le langage ne définit pas si de telles tâches doivent se terminer.

Pour un type d'accès dérivé d'un autre type d'accès, la définition du type d'accès correspondante est celle du type parent; la dépendance est sur le maître élaborant la définition du type d'accès parent ultime.

Une déclaration de renommage définit un nouveau nom pour une entité existante et ne crée donc aucune dépendance supplémentaire.

Entrées, appels d'entrée et instructions Accept

Les appels d'entrée et les instructions Accept sont les principaux moyens de synchronisation des tâches et de communication des valeurs entre les tâches. Une déclaration d'entrée est similaire à une déclaration de sous-programme et n'est autorisée que dans une spécification de tâche. Les actions à effectuer lorsqu'une entrée est appelée sont spécifiées par les instructions Accept correspondantes.

declaration_d_entree ::=
  entry identifiant [(intervalle_discrete)] [partie_formelle];

instruction_d_appel_d_entree ::= nom_entree [partie_parametre_actuel];

declaration_d_acceptation ::=
  accept entree_simple_nom [(index_des_entrees)] [partie_formelle] [do
      sequence_d_instructions
  end [entree_simple_nom]];
index_des_entrees ::= expression

Une déclaration d'entrée incluant un intervalle discret déclare une famille d'entrées distinctes ayant la même partie formelle (le cas échéant); c'est-à-dire une telle entrée pour chaque valeur de la plage discrète. Le terme entrée unique est utilisé dans la définition de toute règle s'appliquant à toute entrée autre qu'une entrée d'une famille. La tâche désignée par un objet d'un type de tâche possède (ou possède) les entrées déclarées dans la spécification du type de tâche.

Dans le corps d'une tâche, chacune de ses entrées uniques ou familles d'entrées peut être nommée par le nom simple correspondant. Le nom d'une entrée d'une famille prend la forme d'un composant indexé, le nom simple de la famille étant suivi de l'index entre parenthèses ; le type de cet index doit être le même que celui de l'intervalle discret dans la déclaration de famille d'entrées correspondante. En dehors du corps d'une tâche, un nom d'entrée a la forme d'un composant sélectionné, dont le préfixe désigne l'objet tâche et dont le sélecteur est le nom simple de l'une de ses entrées uniques ou familles d'entrées. 5 Une entrée unique surcharge un sous-programme, un littéral d'énumération ou une autre entrée unique si elles ont le même identifiant. La surcharge n'est pas définie pour les familles d'entrées. Une entrée unique ou une entrée d'une famille d'entrées peut être renommée en tant que procédure.

Les modes de paramètres définis pour les paramètres de la partie formelle d'une déclaration d'entrée sont les mêmes que pour une déclaration de sous-programme et ont la même signification. La syntaxe d'une instruction d'appel d'entrée est similaire à celle d'une instruction d'appel de procédure, et les règles d'association de paramètres sont les mêmes que pour les appels de sous-programme.

Une instruction accept spécifie les actions à effectuer lors de l'appel d'une entrée nommée (il peut s'agir d'une entrée d'une famille). La partie formelle d'une instruction accept doit être conforme à la partie formelle donnée dans la déclaration de l'entrée unique ou de la famille d'entrées nommée par l'instruction accept. Si un nom simple apparaît à la fin d'une instruction accept, il doit répéter celui donné au début.

Une instruction accept pour une entrée d'une tâche donnée n'est autorisée que dans le corps de la tâche correspondante; à l'exclusion du corps de toute unité de programme étant elle-même interne au corps de la tâche; et à l'exclusion d'une autre instruction accept pour la même entrée unique ou une entrée de la même famille. (Une conséquence de cette règle est qu'une tâche ne peut exécuter des instructions accept que pour ses propres entrées.) Un corps de tâche peut contenir plusieurs instructions accept pour la même entrée. Pour l'élaboration d'une déclaration d'entrée, l'intervalle discret, le cas échéant, est évaluée et la partie formelle, le cas échéant, est ensuite élaborée comme pour une déclaration de sous-programme.

L'exécution d'une instruction accept commence par l'évaluation de l'index d'entrée (dans le cas d'une entrée d'une famille). L'exécution d'une instruction d'appel d'entrée commence par l'évaluation du nom d'entrée; elle est suivie de toutes les évaluations requises pour les paramètres réels de la même manière que pour un appel de sous-programme. L'exécution ultérieure d'une instruction accept et d'une instruction d'appel d'entrée correspondante est synchronisée.

Si une entrée donnée est appelée par une seule tâche, il existe deux possibilités :

Lorsqu'une entrée a été appelée et qu'une instruction accept correspondante a été atteinte, la séquence d'instructions, le cas échéant, de l'instruction accept est exécutée par la tâche appelée (tandis que la tâche appelante reste suspendue). Cette interaction est appelée rendez-vous. Par la suite, la tâche appelante et la tâche propriétaire de l'entrée continuent leur exécution en parallèle.

Si plusieurs tâches appellent la même entrée avant qu'une instruction accept correspondante ne soit atteinte, les appels sont mis en file d'attente; il existe une file d'attente associée à chaque entrée. Chaque exécution d'une instruction accept supprime un appel de la file d'attente. Les appels sont traités dans l'ordre d'arrivée.

Une tentative d'appel d'une entrée d'une tâche ayant terminé son exécution lève l'exception TASKING_ERROR au point d'appel, dans la tâche appelante; de même, cette exception est levée au point d'appel si la tâche appelée termine son exécution avant d'accepter l'appel. L'exception CONSTRAINT_ERROR est levée si l'index d'une entrée d'une famille n'est pas dans l'intervalle discret spécifié.

Exemples de déclarations d'entrée :

  1. entry READ(V:out ITEM);
  2. entry SEIZE;
  3. entry REQUEST(LEVEL)(D : ITEM); -- une famille d'entrées

Exemples d'appels d'entrée :

  1. CONTROL.RELEASE;
  2. PRODUCER_CONSUMER.WRITE(E);
  3. POOL(5).READ(NEXT_CHAR);
  4. CONTROLLER.REQUEST(LOW)(SOME_ITEM);

Exemples d'instructions d'acceptation :

  1. accept SEIZE;
  2.  
  3. accept READ(V:out ITEM) do
  4.   V := LOCAL_ITEM;
  5. end READ;
  6.  
  7. accept REQUEST(LOW)(D : ITEM) do
  8.  ...
  9. end REQUEST;

Remarques : La partie formelle donnée dans une instruction accept n'est pas élaborée; elle sert uniquement à identifier l'entrée correspondante.

Une instruction accept peut appeler des sous-programmes émettant des appels d'entrée. Une instruction accept n'a pas besoin d'avoir une séquence d'instructions même si l'entrée correspondante a des paramètres. De même, elle peut avoir une séquence d'instructions même si l'entrée correspondante n'a pas de paramètres. La séquence d'instructions d'une instruction accept peut inclure des instructions return. Une tâche peut appeler ses propres entrées mais elle se bloquera bien sûr. Le langage autorise les appels d'entrée conditionnels et temporisés. Les règles du langage garantissent qu'une tâche ne peut se trouver que dans une file d'attente d'entrées à un moment donné.

Si les limites de l'intervalle discret d'une famille d'entrées sont des littéraux entiers, l'index (dans un nom d'entrée ou une instruction accept) doit être du type prédéfini INTEGER.

Instructions Delay, Duration et Time

L'exécution d'une instruction de retard évalue l'expression simple et suspend l'exécution ultérieure de la tâche exécutant l'instruction de retard, pendant au moins la durée spécifiée par la valeur résultante.

instruction_delay ::= delay expression_simple

L'expression simple doit être du type à virgule fixe prédéfini DURATION; sa valeur est exprimée en secondes ; une instruction delay avec une valeur négative est équivalente à une instruction delay avec une valeur nulle.

Toute implémentation du type DURATION doit permettre la représentation de durées (positives et négatives) jusqu'à au moins 86 400 secondes (un jour); la plus petite durée représentable, DURATION'SMALL, ne doit pas être supérieure à vingt millisecondes (dans la mesure du possible, une valeur ne dépassant pas cinquante microsecondes doit être choisie). Notez que DURATION'SMALL ne doit pas nécessairement correspondre au cycle d'horloge de base, le nombre nommé SYSTEM.TICK.

La définition du type TIME est fournie dans le paquet de bibliothèque prédéfini CALENDAR. La fonction CLOCK renvoie la valeur actuelle de TIME au moment où elle est appelée. Les fonctions YEAR, MONTH, DAY et SECONDS renvoient les valeurs correspondantes pour une valeur donnée du type TIME; la procédure SPLIT renvoie les quatre valeurs correspondantes. Inversement, la fonction TIME_OF combine un numéro d'année, un numéro de mois, un numéro de jour et une durée, en une valeur de type TIME. Les opérateurs "+" et "-" pour l'addition et la soustraction de temps et de durées, et les opérateurs relationnels pour les temps, ont la signification conventionnelle.

L'exception TIME_ERROR est levée par la fonction TIME_OF si les paramètres réels ne forment pas une date correcte. Cette exception est également levée par les opérateurs "+" et "-" si, pour les opérandes donnés, ces opérateurs ne peuvent pas renvoyer une date dont le numéro d'année est dans l'intervalle du sous-type correspondant, ou si l'opérateur ne peut pas renvoyer un résultat qui est dans l'intervalle du type DURATION.

  1. package CALENDAR is
  2.   type TIME is private;
  3.   
  4.   subtype YEAR_NUMBER  is INTEGER  range 1901 .. 2099;
  5.   subtype MONTH_NUMBER is INTEGER  range 1 .. 12;
  6.   subtype DAY_NUMBER   is INTEGER  range 1 .. 31;
  7.   subtype DAY_DURATION is DURATION range 0.0 .. 86_400.0
  8.  
  9.   function CLOCK return TIME;
  10.  
  11.   function YEAR    (DATE : TIME) return YEAR_NUMBER;
  12.   function MONTH   (DATE : TIME) return MONTH_NUMBER;
  13.   function DAY     (DATE : TIME) return DAY_NUMBER;
  14.   function SECONDS (DATE : TIME) return DAY_DURATION;
  15.  
  16.   procedure SPLIT ( DATE    : in  TIME;
  17.                     YEAR    : out YEAR_NUMBER;
  18.                     MONTH   : out MONTH_NUMBER;
  19.                     DAY     : out DAY_NUMBER;
  20.                     SECONDS : out DAY_DURATION);
  21.  
  22.   function TIME_OF( YEAR    : YEAR_NUMBER;
  23.                     MONTH   : MONTH_NUMBER;
  24.                     DAY     : DAY_NUMBER;
  25.                     SECONDS : DAY_DURATION := 0.0) return TIME;
  26.  
  27.   function "+"  (LEFT : TIME;     RIGHT : DURATION) return TIME;
  28.   function "+"  (LEFT : DURATION; RIGHT : TIME)     return TIME;
  29.   function "-"  (LEFT : TIME;     RIGHT : DURATION) return TIME;
  30.   function "-"  (LEFT : TIME;     RIGHT : TIME)     return DURATION;
  31.   
  32.   function "<"  (LEFT, RIGHT : TIME) return BOOLEAN;
  33.   function "<=" (LEFT, RIGHT : TIME) return BOOLEAN;
  34.   function ">"  (LEFT, RIGHT : TIME) return BOOLEAN;
  35.   function ">=" (LEFT, RIGHT : TIME) return BOOLEAN;
  36.   TIME_ERROR : exception; -- peut être déclenché par TIME_OF, "+" et "-"
  37.  
  38. private
  39. -- dépendant de l'implémentation
  40. end;

Exemples :

  1. delay 3.0; -- retard de 3,0 secondes
  2.  
  3. declare
  4.   use CALENDAR;
  5.   -- INTERVAL est une constante globale de type DURATION
  6.   NEXT_TIME : TIME := CLOCK + INTERVAL;
  7. begin
  8.   loop
  9.     delay NEXT_TIME - CLOCK;
  10.     -- quelques actions
  11.     NEXT_TIME := NEXT_TIME + INTERVAL;
  12.   end loop;
  13. end;

Remarques : Le deuxième exemple provoque la répétition de la boucle toutes les INTERVAL secondes en moyenne. Cet intervalle entre deux itérations successives n'est qu'approximatif. Cependant, il n'y aura pas de dérive cumulative tant que la durée de chaque itération est (suffisamment) inférieure à INTERVAL.

Les instructions de sélection

Il existe trois formes d'instructions de sélection. L'une d'entre elles permet une attente sélective pour une ou plusieurs alternatives. Les deux autres permettent des appels d'entrée conditionnels et temporisés :

instruction_de_selection ::= attente_sélective
  | appel_d_entree_conditionnel | appel_d_entree_chronometre

Attentes sélectives

Cette forme d'instruction select permet une combinaison d'attente et de sélection d'une ou plusieurs alternatives. La sélection peut dépendre des conditions associées à chaque alternative de l'attente sélective :

attente_selective ::=
  select
    selectionner_alternative
  | or
    selectionner_alternative]
  | else
    sequence_d_instructions]
  end select;

selectionner_alternative ::=
  [ when condition =>]
     alternative_d_attente_sélective

alternative_d_attente_sélective ::= accepter_alternative
  | delai_alternatif | terminer_alternative

accepter_alternative ::= declaration_d_acceptation [sequence_d_instructions]

delai_alternatif ::= instruction_delay [sequence_d_instructions]

terminer_alternative ::= terminate;

Une attente sélective doit contenir au moins une alternative d'acceptation. De plus, une attente sélective peut contenir soit une alternative de terminaison (une seule), soit une ou plusieurs alternatives de retard, soit une partie else ; ces trois possibilités s'excluent mutuellement.

Une alternative de sélection est dite ouverte si elle ne commence pas par when et une condition, ou si la condition est TRUE. Elle est dite fermée dans le cas contraire.

Pour l'exécution d'une attente sélective, toutes les conditions spécifiées après when sont évaluées dans un ordre n'étant pas défini par le langage; les alternatives ouvertes sont ainsi déterminées. Pour une alternative de retard ouverte, l'expression de retard est également évaluée. De même, pour une alternative d'acceptation ouverte pour une entrée d'une famille, l'index d'entrée est également évalué. La sélection et l'exécution d'une alternative ouverte, ou de la partie else, achèvent alors l'exécution de l'attente sélective; les règles de cette sélection sont décrites ci-dessous.

Les alternatives d'acceptation ouvertes sont d'abord envisagées. La sélection d'une telle alternative a lieu immédiatement si un rendez-vous correspondant est possible, c'est-à-dire s'il existe un appel d'entrée correspondant émis par une autre tâche et en attente d'acceptation. Si plusieurs alternatives peuvent ainsi être sélectionnées, l'une d'elles est choisie arbitrairement (c'est-à-dire que le langage ne définit pas laquelle). Lorsqu'une telle alternative est sélectionnée, l'instruction accept correspondante et les éventuelles instructions subséquentes sont exécutées. Si aucun rendez-vous n'est immédiatement possible et qu'il n'y a pas de partie else, la tâche attend qu'une alternative ouverte d'attente sélective puisse être sélectionnée.

La sélection des autres formes d'alternative ou d'une partie else s'effectue de la manière suivante :

L'exception PROGRAM_ERROR est levée si toutes les alternatives sont fermées et qu'il n'y a pas de partie else.

Exemples d'une instruction select :

  1. select
  2.   accept DRIVER_AWAKE_SIGNAL;
  3. or
  4.   delay 30.0*SECONDS;
  5.   STOP_THE_TRAIN;
  6. end select;

Exemple de corps de tâche avec une instruction select :

  1. task body RESOURCE is
  2.   BUSY : BOOLEAN := FALSE;
  3. begin
  4.   loop
  5.     select
  6.       when not BUSY =>
  7.         accept SEIZE do
  8.           BUSY := TRUE;
  9.       end;
  10.     or
  11.       accept RELEASE do
  12.           BUSY := FALSE;
  13.       end;
  14.     or
  15.       terminate;
  16.     end select;
  17.   end loop;
  18. end RESOURCE;

Remarques : Une attente sélective peut avoir plusieurs alternatives de délai ouvertes. Une attente sélective peut avoir plusieurs alternatives d'acceptation ouvertes pour la même entrée.

Appels d'entrée conditionnels

Un appel d'entrée conditionnel émet un appel d'entrée étant ensuite annulé si un rendez-vous n'est pas immédiatement possible :

appel_d_entree_conditionnel ::=
  select
    instruction_d_appel_d_entree
    [ sequence_d_instructions]
  else
    sequence_d_instructions
  end select

Pour l'exécution d'un appel d'entrée conditionnel, le nom de l'entrée est d'abord évalué. Ceci est suivi par les évaluations requises pour les paramètres réels comme dans le cas d'un appel de sous-programme.

L'appel d'entrée est annulé si l'exécution de la tâche appelée n'a pas atteint un point où elle est prête à accepter l'appel (c'est-à-dire soit une instruction accept pour l'entrée correspondante, soit une instruction select avec une alternative accept ouverte pour l'entrée), ou s'il existe des appels d'entrée en file d'attente antérieurs pour cette entrée. Si la tâche appelée a atteint une instruction select, l'appel d'entrée est annulé si une alternative accept pour cette entrée n'est pas sélectionnée.

Si l'appel d'entrée est annulé, les instructions de la partie else sont exécutées. Sinon, le rendez-vous a lieu; et la séquence facultative d'instructions après l'appel d'entrée est alors exécutée.

L'exécution d'un appel d'entrée conditionnel déclenche l'exception TASKING_ERROR si la tâche appelée a déjà terminé son exécution.

Exemple :

  1. procedure SPIN(R : RESOURCE) is
  2. begin
  3.   loop
  4.     select
  5.       R.SEIZE;
  6.       return;
  7.     else
  8.       null; -- occupé à attendre
  9.     end select;
  10.   end loop;
  11. end;

Appels d'entrée temporisés

Un appel d'entrée temporisé émet un appel d'entrée étant annulé si un rendez-vous n'est pas démarré dans un délai donné :

appel_d_entrée_chronometre ::=
  select
     instruction_d_appel_d_entree
   [ sequence_d_instructions]
  or
     delai_alternatif
  end select;

Pour l'exécution d'un appel d'entrée temporisé, le nom de l'entrée est d'abord évalué. Ceci est suivi par les évaluations requises pour les paramètres réels comme dans le cas d'un appel de sous-programme. L'expression indiquant le délai est ensuite évaluée et l'appel d'entrée est finalement émis.

Si un rendez-vous peut être démarré dans le délai spécifié (ou immédiatement, comme pour un appel d'entrée conditionnel, pour un délai négatif ou nul), il est exécuté et la séquence optionnelle d'instructions après l'appel d'entrée est alors exécutée. Sinon, l'appel d'entrée est annulé lorsque la durée spécifiée a expiré et la séquence optionnelle d'instructions de l'alternative de délai est exécutée.

L'exécution d'un appel d'entrée temporisé déclenche l'exception TASKING_ERROR si la tâche appelée termine son exécution avant d'accepter l'appel.

Exemple :

  1. select
  2.   CONTROLLER.REQUEST(MEDIUM)(SOME_ITEM);
  3. or
  4.   delay 45.0;
  5.   -- le contrôleur est trop occupé, essayez autre chose
  6. end select;     

Les priorités

Chaque tâche peut (mais n'est pas obligée) avoir une priorité, étant une valeur du sous-type PRIORITY (de type INTEGER) déclarée dans le paquet de bibliothèque prédéfini SYSTEM. Une valeur inférieure indique un degré d'urgence inférieur; l'intervalle de priorités est définie par l'implémentation. Une priorité est associée à une tâche si un pragma :

pragma PRIORITY (expression_statique);

apparaît dans la spécification de tâche correspondante; la priorité est donnée par la valeur de l'expression. Une priorité est associée au programme principal si un tel pragma apparaît dans sa partie déclarative la plus externe. Au plus un pragma de ce type peut apparaître dans une spécification de tâche donnée ou pour un sous-programme étant une unité de bibliothèque, et ce sont les seuls emplacements autorisés pour ce pragma. Un pragma PRIORITY n'a aucun effet s'il apparaît dans un sous-programme autre que le programme principal.

La spécification d'une priorité est une indication donnée pour aider l'implémentation dans l'allocation de ressources de traitement à des tâches parallèles lorsqu'il y a plus de tâches éligibles à l'exécution que ce qui peut être pris en charge simultanément par les ressources de traitement disponibles. L'effet des priorités sur la planification est défini par la règle suivante :

Pour les tâches de même priorité, l'ordre d'ordonnancement n'est pas défini par le langage. Pour les tâches sans priorité explicite, les règles d'ordonnancement ne sont pas définies, sauf lorsque ces tâches sont engagées dans un rendez-vous. Si les priorités des deux tâches engagées dans un rendez-vous sont définies, le rendez-vous est exécuté avec la plus élevée des deux priorités. Si une seule des deux priorités est définie, le rendez-vous est exécuté avec au moins cette priorité. Si aucune n'est définie, la priorité du rendez-vous est indéfinie.

Remarques : La priorité d'une tâche est statique et donc fixe. Cependant, la priorité lors d'un rendez-vous n'est pas nécessairement statique puisqu'elle dépend aussi de la priorité de la tâche appelant l'entrée. Les priorités ne doivent être utilisées que pour indiquer des degrés relatifs d'urgence; elles ne doivent pas être utilisées pour la synchronisation des tâches.

Attributs de tâche et d'entrée

Pour un objet de tâche ou une valeur T, les attributs suivants sont définis :

Attribut Description
T'CALLABLE Renvoie la valeur FALSE lorsque l'exécution de la tâche désignée par T est complétée ou terminée, ou lorsque la tâche est anormale. Renvoie la valeur TRUE dans le cas contraire. La valeur de cet attribut est du type prédéfini BOOLEAN.
T'TERMINATED Renvoie la valeur TRUE si la tâche désignée par T est terminée. Renvoie la valeur FALSE dans le cas contraire. La valeur de cet attribut est de type prédéfini BOOLEAN.

De plus, les attributs de représentation STORAGE_SIZE, SIZE et ADDRESS sont définis pour un objet tâche T ou un type de tâche T.

L'attribut COUNT est défini pour une entrée E d'une unité de tâche T. L'entrée peut être soit une entrée unique, soit une entrée d'une famille (dans les deux cas, le nom de l'entrée unique ou de la famille d'entrées peut être un nom simple ou développé). Cet attribut n'est autorisé que dans le corps de T, mais exclut toute unité de programme étant elle-même interne au corps de T :

Attribut Description
E'COUNT Renvoie le nombre d'appels d'entrée actuellement en file d'attente sur l'entrée E (si l'attribut est évalué par l'exécution d'une instruction accept pour l'entrée E, le décompte n'inclut pas la tâche appelante). La valeur de cet attribut est de type universal_integer.

Remarque : les algorithmes interrogeant l'attribut E'COUNT doivent prendre des précautions pour permettre l'augmentation de la valeur de cet attribut pour les appels d'entrée entrants, et sa diminution, par exemple avec les appels d'entrée temporisés.

L'instruction abort

Une instruction abort provoque le dérèglement d'une ou plusieurs tâches, empêchant ainsi tout rendez-vous ultérieur avec ces tâches :

instruction_abort ::= abort nom_de_tache [, nom_de_tache];

La détermination du type de chaque nom de tâche utilise le fait que le type du nom est un type de tâche.

Pour l'exécution d'une instruction abort, les noms de tâches donnés sont évalués dans un ordre qui n'est pas défini par le langage. Chaque tâche nommée devient alors anormale à moins qu'elle ne soit déjà terminée; de même, toute tâche dépendant d'une tâche nommée devient anormale à moins qu'elle ne soit déjà terminée.

Toute tâche anormale dont l'exécution est suspendue à une instruction accept, select ou delay devient terminée; toute tâche anormale dont l'exécution est suspendue à un appel d'entrée, et qui n'est pas encore dans un rendez-vous correspondant, devient terminée et est supprimée de la file d'attente d'entrée; toute tâche anormale qui n'a pas encore commencé son activation devient terminée (et donc également terminée). Cela termine l'exécution de l'instruction abort.

L'achèvement de toute autre tâche anormale n'a pas besoin de se produire avant l'achèvement de l'instruction abort. Il doit se produire au plus tard lorsque la tâche anormale atteint un point de synchronisation étant l'un des suivants : la fin de son activation ; un point où elle provoque l'activation d'une autre tâche; un appel d'entrée; le début ou la fin d'une instruction accept; d'une instruction select; d'une instruction delay; d'un gestionnaire d'exceptions; ou d'une instruction abort. Si une tâche appelant une entrée devient anormale alors qu'elle est dans un rendez-vous, sa terminaison n'a pas lieu avant la fin du rendez-vous.

L'appel d'une entrée d'une tâche anormale déclenche l'exception TASKING_ERROR à l'endroit de l'appel. De même, l'exception TASKING_ERROR est déclenchée pour toute tâche ayant appelé une entrée d'une tâche anormale, si l'appel d'entrée est toujours en file d'attente ou si le rendez-vous n'est pas encore terminé (que l'appel d'entrée soit une instruction d'appel d'entrée, ou un appel d'entrée conditionnel ou temporisé); l'exception est déclenchée au plus tard à la fin de la tâche anormale. La valeur de l'attribut CALLABLE est FALSE pour toute tâche anormale (ou terminée).

Si la fin anormale d'une tâche a lieu alors que la tâche met à jour une variable, alors la valeur de cette variable est indéfinie.

Exemple :

  1. abort USER, TERMINAL.all, POOL(3);

Remarques : une instruction abort ne doit être utilisée que dans des situations extrêmement graves nécessitant un arrêt inconditionnel. Une tâche est autorisée à abandonner n'importe quelle tâche, y compris elle-même.

Variables partagées

Les moyens habituels de communication de valeurs entre tâches sont les appels d'entrée et les instructions d'acceptation.

Si deux tâches lisent ou mettent à jour une variable partagée (c'est-à-dire une variable accessible par les deux), aucune d'elles ne peut alors supposer quoi que ce soit sur l'ordre dans lequel l'autre effectue ses opérations, sauf aux points où elles se synchronisent. Deux tâches sont synchronisées au début et à la fin de leur rendez-vous. Au début et à la fin de son activation, une tâche est synchronisée avec la tâche provoquant cette activation. Une tâche ayant terminé son exécution est synchronisée avec toute autre tâche.

Pour les actions effectuées par un programme utilisant des variables partagées, les hypothèses suivantes peuvent toujours être faites :

L'exécution du programme est erronée si l'une de ces hypothèses est violée.

Si une tâche donnée lit la valeur d'une variable partagée, les hypothèses ci-dessus permettent à une implémentation de conserver des copies locales de la valeur (par exemple, dans des registres ou dans une autre forme de stockage temporaire); et tant que la tâche donnée n'atteint pas un point de synchronisation ni ne met à jour la valeur de la variable partagée, les hypothèses ci-dessus impliquent que, pour la tâche donnée, la lecture d'une copie locale équivaut à la lecture de la variable partagée elle-même.

De même, si une tâche donnée met à jour la valeur d'une variable partagée, les hypothèses ci-dessus permettent à une implémentation de conserver une copie locale de la valeur et de différer l'entreposage effectif de la copie locale dans la variable partagée jusqu'à un point de synchronisation, à condition que chaque lecture ou mise à jour supplémentaire de la variable par la tâche donnée soit traitée comme une lecture ou une mise à jour de la copie locale. D'autre part, une implémentation n'est pas autorisée à introduire un entreposage, à moins que ce stockage ne soit également exécuté dans l'ordre canonique.

Le pragma SHARED peut être utilisé pour spécifier que chaque lecture ou mise à jour d'une variable est un point de synchronisation pour cette variable ; c'est-à-dire que les hypothèses ci-dessus sont toujours valables pour la variable donnée (mais pas nécessairement pour les autres variables). La forme de ce pragma est la suivante :

pragma SHARED(variable_simple_name);

Ce pragma n'est autorisé que pour une variable déclarée par une déclaration d'objet et dont le type est un type scalaire ou d'accès; la déclaration de variable et le pragma doivent tous deux apparaître (dans cet ordre) immédiatement dans la même partie déclarative ou spécification de paquet; le pragma doit apparaître avant toute occurrence du nom de la variable, autre que dans une clause d'adresse.

Une implémentation doit restreindre les objets pour lesquels le pragma SHARED est autorisé aux objets pour lesquels la lecture directe et la mise à jour directe sont implémentées comme une opération indivisible.

Exemple de tâche

L'exemple suivant définit une tâche de mise en mémoire tampon pour lisser les variations entre la vitesse de sortie d'une tâche de production et la vitesse d'entrée d'une tâche de consommation. Par exemple, la tâche de production peut contenir les instructions suivantes :

  1. loop
  2.   -- produire le caractère suivant CHAR
  3.   BUFFER.WRITE(CHAR);
  4.   exit when CHAR = ASCII.EOT;
  5. end loop;

et la tâche consommatrice peut contenir les instructions :

  1. loop
  2.   BUFFER.READ(CHAR);
  3.   -- consommer le caractère CHAR
  4.   exit when CHAR = ASCII.EOT;
  5. end loop;

La tâche de mise en mémoire tampon contient un bassin interne de caractères traités de manière circulaire. Le pool possède deux index, un IN_INDEX indiquant l'espace pour le prochain caractère d'entrée et un OUT_INDEX indiquant l'espace pour le prochain caractère de sortie :

  1. task BUFFER is
  2.   entry READ (C : out CHARACTER);
  3.   entry WRITE (C : in CHARACTER);
  4. end;
  5.  
  6. task body BUFFER is
  7.   POOL_SIZE : constant INTEGER := 100;
  8.   POOL : array( 1 .. POOL_SIZE) of CHARACTER;
  9.   COUNT : INTEGER range 0 .. POOL_SIZE := 0;
  10.   IN_INDEX, OUT_INDEX : INTEGER range 1 .. POOL_SIZE := 1;
  11. begin
  12.   loop
  13.     select
  14.       when COUNT < POOL_SIZE =>
  15.         accept WRITE(C : in CHARACTER) do
  16.           POOL(IN_INDEX) := C;
  17.         end;
  18.         IN_INDEX := IN_INDEX mod POOL_SIZE + 1;
  19.         COUNT := COUNT + 1;
  20.       or when COUNT > 0 =>
  21.         accept READ(C : out CHARACTER) do
  22.           C := POOL(OUT_INDEX);
  23.         end;
  24.         OUT_INDEX := OUT_INDEX mod POOL_SIZE + 1;
  25.         COUNT := COUNT - 1;
  26.       or
  27.         terminate;
  28.     end select;
  29.   end loop;
  30. end BUFFER;


PARTAGER CETTE PAGE SUR
Dernière mise à jour : Mardi, le 7 janvier 2025