Section courante

A propos

Section administrative du site

Programmation de processus léger

Le Free Pascal prend en charge la programmation des processus léger : il existe une construction de langage disponible pour l'entreposage local des processus légers (ThreadVar), et des routines de processus léger de bas niveau multiplateformes sont disponibles pour les systèmes d'exploitation prenant en charge les processus léger.

Toutes les routines de processus léger sont disponibles dans l'unité centrale, sous la forme d'un gestionnaire de processus léger. Un gestionnaire de processus léger doit implémenter certaines routines de base dont le RTL a besoin pour pouvoir prendre en charge le processus léger. Pour Windows, un gestionnaire de processus léger par défaut est intégré à l'unité centrale. Pour les autres plateformes, un gestionnaire de processus légers doit être inclus explicitement par le programmeur. Sur les systèmes où les processus légers posix sont disponibles, l'unité cthreads implémente un gestionnaire de processus légers utilisant la bibliothèque de processus léger du C de POSIX. Aucune bibliothèque de processus léger Pascal native n'existe pour de tels systèmes.

Bien que cela ne soit pas interdit, il n'est pas recommandé d'utiliser des routines de processus léger spécifiques au système : le support du langage pour les programmes multi-processus léger ne sera pas activé, ce qui signifie que les ThreadVar ne fonctionneront pas, le gestionnaire de mémoire de tas sera confus, ce qui peut entraîner de graves problèmes et des erreurs de programme.

Si aucun support de processus léger n'est présent dans le binaire, l'utilisation de routines de processus léger ou la création d'un processus léger entraînera une exception ou une erreur d'exécution 232.

Pour Linux (et autres Unix), le gestionnaire de processus léger C peut être activé en insérant l'unité cthreads dans la clause Unit du programme. Sans cela, les programmes de processus léger généreront une erreur au démarrage. Il est impératif que l'unité soit insérée le plus tôt possible dans la clause d'utilisation. Ultérieurement, un gestionnaire de processus léger système peut être implémenté pour implémenter des processus légers sans prise en charge de Libc.

Les sections suivantes montrent comment programmer des processus léger et comment protéger l'accès aux données communes à tous les processus léger à l'aide de sections critiques (multiplateformes). Enfin, le gestionnaire de processus léger est expliqué plus en détail.

Processus léger de programmation

Pour démarrer un nouveau processus léger, la fonction BeginThread doit être utilisée. Il a un paramètre obligatoire : la fonction étant exécutée dans le nouveau processus léger. Le résultat de la fonction est le résultat de sortie du processus léger. La fonction de processus léger peut recevoir un pointeur, pouvant être utilisé pour accéder aux données d'initialisation : le programmeur doit s'assurer que les données sont accessibles à partir du processus léger et ne sortent pas de la portée avant que le processus léger n'y ait accédé.

  1. Type
  2.  TThreadFunc=Function(Parameter:Pointer):PtrInt;
  3.  
  4. Function BeginThread(SA:Pointer;StackSize:SizeUInt;ThreadFunction:TThreadFunc;P:Pointer;CreationFlags:DWord;Var ThreadId:TThreadID):TThreadID;

Cette forme complète plutôt compliquée de la fonction se présente également sous des formes plus simplifiées :

  1. Function BeginThread(ThreadFunction:TThreadFunc):TThreadID;
  2. Function BeginThread(ThreadFunction:TThreadFunc;P:Pointer):TThreadID;
  3. Function BeginThread(ThreadFunction:TThreadFunc;P:Pointer;Var ThreadId:TThreadID):TThreadID;
  4. Function BeginThread(ThreadFunction:TThreadFunc;P:Pointer;Var ThreadId:TThreadID;Const StackSize:SizeUInt):TThreadID;

Les paramètres ont la signification suivante :

Paramètre Description
ThreadFunction Est la fonction devant être exécutée dans le processus léger.
P S'il est présent, le pointeur p sera passé à la fonction de processus léger lors de son démarrage. Si p n'est pas spécifié, Nil est transmis.
ThreadID Si ThreadID est présent, l'identificateur du processus léger y sera entreposé.
StackSize S'il est présent, ce paramètre spécifie la taille de la pile utilisée pour le processus léger.
SA Signaler une action. Important pour Linux uniquement.
CreationFlags Ce sont des drapeaux de création spécifiques au système. Important pour Windows et OS/2 uniquement.

Le processus léger nouvellement démarré s'exécutera jusqu'à ce que la ThreadFunction quitte, ou jusqu'à ce qu'il appelle explicitement la fonction EndThread :

  1. {$mode objfpc}
  2.  
  3. Uses SysUtils {$IFDEF unix},cthreads{$ENDIF};
  4.  
  5. Const
  6.  ThreadCount=100;
  7.  StringLen=10000;
  8.  
  9. Var
  10.  Finished:LongInt;
  11.  
  12. Threadvar
  13.  Thri:PtrInt;
  14.  
  15. Function F(P:Pointer):PtrInt;
  16. Var
  17.  S:AnsiString;
  18. Begin
  19.  Writeln('Processus léger ',LongInt(P),' démarré');
  20.  Thri:=0;
  21.  While(Thri<StringLen)do Begin
  22.   S:=S+'1';
  23.   Inc(Thri);
  24.  End;
  25.  Writeln('Processus léger ',LongInt(P),' arrêté');
  26.  InterLockedIncrement(finished);
  27.  F:=0;
  28. End;
  29.  
  30. Var
  31.  I:LongInt;
  32.  
  33. BEGIN
  34.  Finished:=0;
  35.  For I:=1 to ThreadCount do BeginThread(@f,Pointer(I));
  36.  while Finished<ThreadCount do;
  37.  WriteLn(Finished);
  38. END.

InterLockedIncrement est une version thread-safe de la fonction Inc standard.

Pour fournir une prise en charge indépendante du système pour la programmation des processus léger, certaines fonctions utilitaires sont implémentées pour manipuler les processus légers. Pour utiliser ces fonctions, l'identificateur du processus léger doit avoir été récupéré au démarrage du processus léger, car la plupart des fonctions nécessitent l'identificateur pour identifier le processus léger sur lequel elles doivent agir :

  1. Function SuspendThread(ThreadHandle:TThreadID):DWord;
  2. Function ResumeThread(ThreadHandle:TThreadID):DWord;
  3. Function KillThread(ThreadHandle:TThreadID):DWord;
  4. Function WaitForThreadTerminate(ThreadHandle:TThreadID;TimeoutMs:LongInt):DWord;
  5. Function ThreadSetPriority(ThreadHandle:TThreadID;Prio:LongInt):Boolean;
  6. Function ThreadGetPriority(ThreadHandle:TThreadID):Integer;
  7. Function GetCurrentThreadId:DWord;
  8. Procedure ThreadSwitch;

La signification de ces fonctions doit être claire :

Fonction Description
SuspendThread Suspend l'exécution du processus léger.
ResumeThread Reprend l'exécution d'un processus léger suspendu.
KillThread Tue le processus léger : le processus léger est supprimé de la mémoire.
WaitForThreadTerminate Attend que le processus léger se termine. La fonction est renvoyée lorsque le processus léger a fini de s'exécuter ou lorsque le délai d'attente a expiré.
ThreadSetPriority Définit la priorité d'exécution du processus léger. Cet appel n'est pas toujours autorisé : votre processus peut ne pas disposer des autorisations nécessaires pour le faire.
ThreadGetPriority Renvoie la priorité d'exécution actuelle du processus léger.
GetCurrentThreadId Renvoie l'identificateur du processus léger actuel.
ThreadSwitch Permet à d'autres processus léger de s'exécuter à ce stade. Cela signifie que cela peut provoquer un changement de processus léger, mais cela n'est pas garanti, cela dépend du système d'exploitation et du nombre de processeurs.

Sections critiques

Lors de la programmation de processus léger, il est parfois nécessaire d'éviter l'accès simultané à certaines ressources, ou d'éviter qu'une certaine routine soit exécutée par deux processus léger. Cela peut être fait en utilisant une section critique. Le gestionnaire de mémoire de tas FPC utilise des sections critiques lorsque le multi-processus léger est activé.

Le type TRTLCriticalSection est un type Opaque; cela dépend du système d'exploitation sur lequel le code est exécuté. Il doit être initialisé avant sa première utilisation et doit être éliminé lorsqu'il n'est plus nécessaire.

Pour protéger un morceau de code, un appel à EnterCriticalSection doit être effectué : lorsque cet appel revient, il est garanti que le processus léger actuel est le seul à exécuter le code suivant. L'appel peut avoir suspendu le processus léger en cours pour une durée indéterminée pour garantir cela.

Lorsque le code protégé est terminé, LeaveCriticalSection doit être appelé : cela permettra à d'autres processus léger de commencer à exécuter le code protégé. Pour minimiser le temps d'attente des processus léger, il est important de garder le bloc protégé aussi petit que possible.

La définition de ces appels est la suivante :

  1. Procedure InitCriticalSection(Var CS:TRTLCriticalSection);
  2. Procedure DoneCriticalSection(Var CS:TRTLCriticalSection);
  3. Procedure EnterCriticalSection(Var CS:TRTLCriticalSection);
  4. Procedure LeaveCriticalSection(Var CS:TRTLCriticalSection);

La signification de ces appels est encore une fois presque évidente :

Procédure Description
InitCriticalSection Initialise une section critique. Cet appel doit être effectué avant l'utilisation de EnterCriticalSection ou LeaveCriticalSection.
DoneCriticalSection Libère les ressources associées à une section critique. Après cet appel, EnterCriticalSection ou LeaveCriticalSection peuvent être utilisés.
EnterCriticalSection Lorsque cet appel est renvoyé, le processus léger appelant est le seul processus léger exécutant le code entre l'appel EnterCriticalSection et l'appel LeaveCriticalsection suivant.
LeaveCriticalSection Signale que le code protégé peut être exécuté par d'autres processus léger.

Notez que l'appel LeaveCriticalsection doit être exécuté. Ne pas le faire empêchera tous les autres processus léger d'exécuter le code dans la section critique. Il est donc recommandé de placer la section critique dans un bloc Try..Finally. Généralement, le code ressemblera à ceci :

  1. Var
  2.  MyCS:TRTLCriticalSection;
  3.  
  4. Procedure CriticalProc;Begin
  5.  EnterCriticalSection(MyCS);
  6.  Try
  7.   { Code protégé }
  8.  Finally
  9.   LeaveCriticalSection(MyCS);
  10.  End;
  11. End;
  12.  
  13. Procedure ThreadProcedure;Begin
  14.   { Code exécuté dans les processus léger... }
  15.  CriticalProc;
  16.   { Plus de code exécuté dans les processus léger... }
  17. End;
  18.  
  19. BEGIN
  20.  InitCriticalSection(MyCS);
  21.  { Code pour démarrer les processus léger. }
  22.  DoneCriticalSection(MyCS);
  23. END.

Le gestionnaire de processus léger

Tout comme la mémoire de tas est implémenté à l'aide d'un gestionnaire de mémoire de tas et que la gestion des chaînes de caractères Unicode est laissée à un gestionnaire de chaînes de caractères Unicode, les processus léger ont été implémentés à l'aide d'un gestionnaire de processus léger. Cela signifie qu'il existe un enregistrement contenant des champs de type procédural pour toutes les fonctions possibles utilisées dans les routines de processus léger. Les routines de processus léger utilisent ces champs pour effectuer le travail réel.

Les routines de processus léger installent un gestionnaire de processus léger système spécifique à chaque système. Sous Windows, les routines Windows normales sont utilisées pour implémenter les fonctions du gestionnaire de processus léger. Sous Linux et autres Unix, le gestionnaire de processus léger système ne fait rien : il générera une erreur lorsque des routines de processus léger seront utilisées. La raison est que les routines de gestion des processus léger se trouvent dans la bibliothèque C. L'implémentation du gestionnaire de processus léger système rendrait le RTL dépendant de la bibliothèque C, ce qui n'est pas souhaitable. Pour éviter la dépendance à la bibliothèque C, le Thread Manager est implémenté dans une unité distincte (cthreads). Le code d'initialisation de cette unité définit le gestionnaire de processus léger sur un enregistrement de gestionnaire de processus léger utilisant les routines C (pthreads).

L'enregistrement du gestionnaire de processus léger peut être récupéré et défini comme l'enregistrement du gestionnaire de mémoire de tas. Le dossier se présente (actuellement) comme suit :

  1. Type
  2.  TThreadManager=Record
  3.   InitManager:Function:Boolean;
  4.   DoneManager:Function:Boolean;
  5.   BeginThread:TBeginThreadHandler;
  6.   EndThread:TEndThreadHandler;
  7.   SuspendThread:TThreadHandler;
  8.   ResumeThread:TThreadHandler;
  9.   KillThread:TThreadHandler;
  10.   ThreadSwitch:TThreadSwitchHandler;
  11.   WaitForThreadTerminate:TWaitForThreadTerminateHandler;
  12.   ThreadSetPriority:TThreadSetPriorityHandler;
  13.   ThreadGetPriority:TThreadGetPriorityHandler;
  14.   GetCurrentThreadId:TGetCurrentThreadIdHandler;
  15.   InitCriticalSection:TCriticalSectionHandler;
  16.   DoneCriticalSection:TCriticalSectionHandler;
  17.   EnterCriticalSection:TCriticalSectionHandler;
  18.   LeaveCriticalSection:TCriticalSectionHandler;
  19.   InitThreadVar:TInitThreadVarHandler;
  20.   RelocateThreadVar:TRelocateThreadVarHandler;
  21.   AllocateThreadVars:TAllocateThreadVarsHandler;
  22.   ReleaseThreadVars:TReleaseThreadVarsHandler;
  23.  End;

La signification de la plupart de ces fonctions devrait ressortir clairement des descriptions des sections précédentes.

InitManager et DoneManager sont appelés lorsque le gestionnaire de processus léger est défini (InitManager) ou lorsqu'il n'est pas défini (DoneManager). Ils peuvent être utilisés pour initialiser le gestionnaire de processus léger ou pour nettoyer une fois l'opération terminée. Si l'un d'eux renvoie False, l'opération échoue.

Il y a quelques entrées spéciales dans l'enregistrement, liées à la gestion des variables de processus léger :

Champ Description
InitThreadVar Ce champ est appelé lorsqu'une variable de processus léger doit être initialisée. Sa syntaxe est du genre :

TInitThreadVarHandler=Procedure(Var Offset:DWord;Size:DWord);

Le paramètre Offset indique le déplacement dans le bloc de variables de processus léger : Toutes les variables de processus léger sont situées dans un seul bloc, les unes après les autres. Le paramètre Size indique la taille de la variable thread. Cette fonction sera appelée une fois pour toutes les variables de processus léger du programme.
RelocateThreadVar Ce champ est appelé à chaque démarrage d'un processus léger, et une fois pour le processus léger principal. Il est du type :

TRelocateThreadVarHandler=Function(Offset:DWord):Pointer;

Il doit renvoyer le nouvel emplacement de la variable locale du processus léger.
AllocateThreadVars Ce champ est appelé lorsque de l'espace doit être alloué à toutes les variables de processus léger pour un nouveau processus léger. C'est une procédure simple, sans paramètres. La taille totale des ThreadVars est entreposée par le compilateur dans la variable globale ThreadVarBlockSize. Le gestionnaire de mémoire de tas ne peut pas être utilisé dans cette procédure : le gestionnaire de mémoire de tas lui-même utilise des ThreadVars n'ayant pas encore été alloués.
ReleaseThreadVars Cette procédure (sans paramètres) est appelée lorsqu'un processus léger se termine et toute la mémoire allouée doit être à nouveau libérée.


Dernière mise à jour : Dimanche, le 27 août 2023