Difference between revisions of "Multithreaded Application Tutorial/fr"

From Lazarus wiki
Jump to navigationJump to search
m (Fixed syntax highlighting)
 
(44 intermediate revisions by 5 users not shown)
Line 6: Line 6:
 
Une application multi-tâches est celle qui crée deux tâches d'execution ou plus en même temps.
 
Une application multi-tâches est celle qui crée deux tâches d'execution ou plus en même temps.
  
Si vous êtes un nouveau dans le multi-tâches,veuillez lire le paragraphe  "Avez vous besoin du multi-tâches ?" pour découvrir , si vous avez vraiment besoin de ça. Vous pouvez vous éviter beaucoup de maux de tête .
+
Si vous êtes nouveau dans le multi-tâches, veuillez lire le paragraphe  "Avez vous besoin du multi-tâches ?" pour découvrir si vous en avez vraiment besoin (Vous pouvez vous éviter beaucoup de maux de tête).
  
Une des tâches légères s'appelle la tâche principale . La tâche principale est celle qui est créé par le logiciel d'exploitation , une fois que notre application commence .
+
Une des tâches (threads) s'appelle la tâche principale . La tâche principale est celle qui est créé par le système d'exploitation lorsque notre application démarre. La tâche principale '''doit être ''' la seule tâche qui met à jour les composants  qui interfèrent avec l'utilisateur (autrement , l'application risque de planter ).
La tâche principale '''doit être ''' la seule tâche qui met à jour les composants  qui interfèrent avec l'utilisateur (autrement , l'application peut accrocher ).
 
  
L'idée principale est que l'application peut faire quelques traitements en arrière-plan (dans une deuxième tâche) tandis que l'utilisateur peut continuer à travailler  (en employant la tâche principale).
+
L'idée principale est que l'application peut faire quelques traitements en arrière-plan (dans une deuxième tâche) tandis que l'utilisateur peut continuer à travailler  (en utilisant la tâche principale).
  
Une autre utilisation des tâches est juste pour avoir une application qui répond mieux. Si vous créez une application, et quand l'utilisateur appuie sur un bouton, l'application commence le traitement(un gros travail )... et pendant le traitement , L'écran arrête de répondre, et donne à l'utilisateur la sensation que l'application est morte,ce n'est pas si agréable. Si le gros travail fonctionne dans une deuxième tâche, l'application continue à répondre (presque ) comme si elle était en idle. Dans ce cas-ci c'est une bonne idée, avant de commencer la tâche, pour neutraliser les boutons de la fiche pour éviter à l'utilisateur de commencer plus d'une tâche pour le travail .
+
L'utilisation des tâches permet aussi d'avoir une application qui répond mieux. Si vous créez une application, et que, quand l'utilisateur appuie sur un bouton, l'application commence le traitement (un gros travail )... pendant le traitement , L'écran arrête de répondre, et donne à l'utilisateur la sensation que l'application est plantée, ce n'est pas très agréable. Si le gros travail fonctionne dans une deuxième tâche, l'application continue à répondre (presque ) comme si elle était en idle. Dans ce cas, il est préférable avant de commencer la tâche, de neutraliser les boutons de la fiche pour éviter que l'utilisateur ne démarre plus d'une tâche de travail .
  
 
Une autre utilisation , est de créer une application serveur qui est capable de répondre à beaucoup de clients en même temps .
 
Une autre utilisation , est de créer une application serveur qui est capable de répondre à beaucoup de clients en même temps .
Line 19: Line 18:
 
= Avez vous besoin du multi-tâche? =
 
= Avez vous besoin du multi-tâche? =
  
Si vous êtes un novice avec le multi-tâche et vous voulez seulement rendre votre application plus nerveuse tandis que votre application calcule une grande charge de travail, alors le multi-tâche ne devrait pas être, ce que vous recherchez.
+
Si vous êtes un novice dans le multi-tâche et que vous souhaitez simplement rendre votre application plus nerveuse lorsqu'elle a une grande charge de travail, alors le multi-tâche n'est peut-être pas ce que vous recherchez.
Les applications multi-tâches sont toujours plus difficile à debogguer et elles sont souvent beaucoup plus complexes. Et dans beaucoup de cas vous n'avez pas besoin du multi-tâche . Une seule tâche est suffisante. Si vous pouvez fractionner les longues corvées de travail en plusieurs petits morceaux, alors à la place vous devriez employer  '''Application.ProcessMessages'''. Cette méthode laisse la LCL traiter tous les messages d'attente et ceux de retour .
+
Les applications multi-tâches sont toujours plus difficile et à debogguer, elles sont souvent beaucoup plus complexes. Dans beaucoup de cas vous n'avez pas besoin du multi-tâche, une seule tâche est suffisante. Si vous pouvez fractionner les longues corvées de travail en plusieurs petits morceaux, utilisez plutôt '''Application.ProcessMessages''', cette méthode laisse la LCL traiter tous les messages en attente et en retour. L'idée est de traiter une partie du travail, puis d'appeller Application.ProcessMessages pour voir si l'utilisateur abandonne ou clique quelque part, ou d'actualiser un indicateur de progression, et ensuite de continuer avec la partie suivante du travail, appeler Application.ProcessMessages et ainsi de suite. Pensez également, lors de l'exécution d'une application externe (à la manière d'un script) de laisser du temps à l'éventuel système tiers de ''digérer'' la commande en mettant votre application en pause à l'aide de la commande '''sleep('''''miliseconds''''')''' de l'unité sysutils.
L'idée est de traiter une partie du travail, puis d'appeller Application.ProcessMessages pour voir si l'utilisateur abandonne ou clique quelque part ou si l'indicateur de processus peint , ensuite continuer avec la partie suivante du travail, appeller  Application.ProcessMessages et ainsi de suite .
 
 
 
This method let the LCL handle all waiting messages and returns. The idea is to process a part of the work, then call Application.ProcessMessages to see if the User aborts or clicked somewhere or the process indicator repaints, then continue with the next part of the work, call Application.ProcessMessages and so forth.
 
  
 
Par exemple : Lecture d'un grand fichier et son traitement .  
 
Par exemple : Lecture d'un grand fichier et son traitement .  
 
Voir examples/multithreading/singlethreadingexample1.lpi.
 
Voir examples/multithreading/singlethreadingexample1.lpi.
  
Multithreading is only needed for
+
Le multi-tâche est seulement nécessaire pour
* blocking handles, like network communications
+
* le blocage des handles, comme les communications de réseau
* using multiple processors at once
+
* l'utilisation de plusieurs processeurs à la fois
* algorithms and library calls, that can not be split up into small parts.
+
* les algorithmes et les appels de bibliothèque, qui ne peuvent pas être fractionné dans de petites pièces.
  
= The TThread Class =
+
= La classe TThread =
  
The following example can be found in the examples/multithreading/ directory.
+
L'exemple suivant se trouve dans le répertoire examples/multithreading/.
  
To create a multithreaded application, the easiest way is to use the TThread Class.
+
Pour créer une application multi-tâches, la manière la plus simple est d'utiliser la classe TThread.
  
This class permits the creation of an additional thread (alongside the main thread) in a simple way.
+
Cette classe permet la création d'une tâche supplémentaire (à côté de la tâche principale) de manière simple.
  
Normally you only have to override 2 methods: the Create constructor, and the Execute method.
+
Normalement, il suffit de surcharger 2 méthodes: le constructeur Create, et la méthode Execute.
  
In the constructor, you will prepare the thread to run.
+
Dans le constructeur, vous préparez la tâche à s'exécuter, vous fixerez les valeurs initiales des variables ou des propriétés que vous souhaitez. Le constructeur original de TThread exige un paramètre appelé Suspended.  
You will set the initial values of the variables or properties you need. The original constructor of TThread requires a parameter called Suspended.  
+
Comme vous pourriez le prévoir, fixer Suspended = True empêchera la tâche de commencer automatiquement après sa création .  
As you might expect, setting Suspended = True will prevent the thread starting automatically after the creation.  
+
Si  Suspended = False, la tâche commencera à fonctionner juste après sa création, si Suspended = True elle ne fonctionnera qu'après l'appel de la méthode Start.
If Suspended = False, the thread will start running just after the creation.
 
If the thread is created suspended, then it will run only after the Resume method is called.
 
  
As of FPC version 2.0.1 and later, TThread.Create also has an implicit parameter for Stack Size.
+
{{Note|La méthode Resume est [http://wiki.freepascal.org/User_Changes_2.4.4#TThread.Suspend_and_TThread.Resume_have_been_deprecated obsolète depuis FPC 2.4.4]. Elle est remplacée par Start.}}
You can now change the default stack size of each thread you create if you need it.
 
Deep procedure call recursions in a thread are a good example. If you don't specify the stack size parameter, a default OS stack size is used.
 
  
In the overrided Execute method you will write the code that will run on the thread.
+
A partir de FPC version 2.0.1 et au delà, TThread.Create a également un paramètre implicite de taille de la pile. Vous pouvez maintenant changer la taille de la pile par défaut pour chaque tâche que vous créez (si vous en avez besoin). Les appels de procedure récursifs dans une tâche sont un bon exemple. Si vous n'indiquez pas le paramètre de la taille de la pile, la taille de pile de l'OS par défaut est employée.
  
The TThread class has one important property:  
+
Dans la méthode surchargée Execute vous écrivez le code qui fonctionnera dans la tâche.
 +
 
 +
La classe TThread a une propriété importante :  
 
Terminated : boolean;
 
Terminated : boolean;
  
If the thread has a loop (and this is usual), the loop should be exited when Terminated is true (it is false by default). So in each cycle, it must check if Terminated is True, and if it is, must exit the .Execute method as quickly as possible, after any necessary cleanup.
+
Si la tâche est une boucle (ce qui est habituel), la boucle devra être quittée quand Terminated passera à True (sa valeur est False par défaut). Ainsi dans chaque cycle, la tâche doit tester si Terminated vaut True, et si c'est le cas, doit sortir de la méthode Execute aussi rapidement que possible (après tout nettoyage nécessaire).
 
 
So keep in mind that the Terminate method does not do anything by default: the .Execute method must explicitly implement support for it to quit it's job.
 
  
As we explained earlier, the thread should not interact with the visible components. To show something to the user it must do so in the main thread.
+
Gardez aussi à l'esprit que la méthode Terminate ne fait rien par défaut: la méthode Execute '''doit''' explicitement faire le nécessaire pour stopper son travail.
To do this, a TThread method called Synchronize exists.
 
Synchronize requires a method (that takes no parameters) as an argument.
 
When you call that method through Synchronize(@MyMethod), the thread execution will be paused, the code of MyMethod will run in the main thread, and then the thread execution will be resumed. The exact working of Synchronize depends on the platform, but basically it does this: It posts a message onto the main message queue and goes to sleep. Eventually the main thread processes the message and calls MyMethod. This way MyMethod is called without context, that means not during a mouse down event or during paint event, but after. After the main thread executed MyMethod, it wakes the sleeping Thread and processes the next message. The Thread then continues.
 
  
There is another important property of TThread: FreeOnTerminate. If this property is true, the thread object is automatically freed when the thread execution (.Execute method) stops. Otherwise the application will need to free it manually.
+
Comme nous l'avons expliqué plus tôt, la tâche ne devra pas interagir avec les composants visuels. Pour interagir avec l'utilisateur elle devra le faire dans la tâche principale. Pour faire cela, il faut utiliser la méthode Synchronize de la classe TThread. Synchronize prend en paramètre une méthode (qui, elle, ne prend aucun paramètre) comme argument. Quand vous appelez cette méthode au moyen de Synchronize(@MyMethod), l'execution de la tâche sera mis en pause, le code de  MyMethod s'exécutera dans la tâche principale, et ensuite l'exécution de la tâche reprendra. Le fonctionnement exact de  Synchronize dépend de la plate-forme, mais fondamentalement elle fait ceci: Elle se signale par un message sur la file d'attente de message principale et va dormir. Par la suite, lorsque la tâche principale traite ce message,  elle appelle MyMethod. De cette façon  MyMethod est appelé hors contexte, ce qui signifie '''pas pendant''' un évènement de déplacement de souris ou pendant un événement de peinture, '''mais après'''. Dès que la tâche principale a exécuté MyMethod, elle réveille la tâche en sommeil et exécute le message suivant. La tâche continue alors.
  
Example:
+
Il y a une autre propriété importante de la classe TThread: FreeOnTerminate. Si cette propriété est true, l'objet tâche est automatiquement libéré quand l'execution de la tâche (méthode .Execute) s'arrête, sinon l'application devra le libérer manuellement.
  
 +
Exemple :
 +
<syntaxhighlight lang=pascal>
 
   Type
 
   Type
 
     TMyThread = class(TThread)
 
     TMyThread = class(TThread)
Line 81: Line 72:
 
       Constructor Create(CreateSuspended : boolean);
 
       Constructor Create(CreateSuspended : boolean);
 
     end;
 
     end;
 
+
</syntaxhighlight>
 +
<syntaxhighlight lang=pascal>
 
   constructor TMyThread.Create(CreateSuspended : boolean);
 
   constructor TMyThread.Create(CreateSuspended : boolean);
 
   begin
 
   begin
Line 87: Line 79:
 
     inherited Create(CreateSuspended);
 
     inherited Create(CreateSuspended);
 
   end;
 
   end;
 
+
</syntaxhighlight>
 +
<syntaxhighlight lang=pascal>
 
   procedure TMyThread.ShowStatus;
 
   procedure TMyThread.ShowStatus;
 
   // this method is executed by the mainthread and can therefore access all GUI elements.
 
   // this method is executed by the mainthread and can therefore access all GUI elements.
Line 93: Line 86:
 
     Form1.Caption := fStatusText;
 
     Form1.Caption := fStatusText;
 
   end;
 
   end;
+
</syntaxhighlight>
 +
<syntaxhighlight lang=pascal>
 
   procedure TMyThread.Execute;
 
   procedure TMyThread.Execute;
 
   var
 
   var
Line 113: Line 107:
 
       end;
 
       end;
 
   end;
 
   end;
 
+
</syntaxhighlight>
On the application,
+
Voici l'application :
 
+
<syntaxhighlight lang=pascal>
 
   var
 
   var
 
     MyThread : TMyThread;
 
     MyThread : TMyThread;
Line 123: Line 117:
 
     [Here the code initialises anything required before the threads starts executing]
 
     [Here the code initialises anything required before the threads starts executing]
 
     ...
 
     ...
     MyThread.Resume;
+
     MyThread.Start;
 
   end;
 
   end;
 +
</syntaxhighlight>
 +
Si vous voulez rendre votre synchronisation plus flexible vous pouvez créer un événement pour cette tâche - de cette façon votre méthode synchronized ne sera pas étroitement couplée avec une fiche spécifique ou une classe - il suffit ensuite d'avoir une méthode répondant à l'évènement du thread. Voici un exemple :
  
If you want to make your application more flexible you can create an event for the thread - this way your synchronized method won't be tightly coupled with a specific form or class - you can attach listeners to the thread's event. Here is an example:
+
<syntaxhighlight lang=pascal>
 
 
 
 
 
   Type
 
   Type
 
     TShowStatusEvent = procedure(Status: String) of Object;
 
     TShowStatusEvent = procedure(Status: String) of Object;
Line 143: Line 137:
 
       property OnShowStatus: TShowStatusEvent read FOnShowStatus write FOnShowStatus;
 
       property OnShowStatus: TShowStatusEvent read FOnShowStatus write FOnShowStatus;
 
     end;
 
     end;
 
+
</syntaxhighlight>
 +
<syntaxhighlight lang=pascal>
 
   constructor TMyThread.Create(CreateSuspended : boolean);
 
   constructor TMyThread.Create(CreateSuspended : boolean);
 
   begin
 
   begin
Line 149: Line 144:
 
     inherited Create(CreateSuspended);
 
     inherited Create(CreateSuspended);
 
   end;
 
   end;
 
+
</syntaxhighlight>
 +
<syntaxhighlight lang=pascal>
 
   procedure TMyThread.ShowStatus;
 
   procedure TMyThread.ShowStatus;
 
   // this method is executed by the mainthread and can therefore access all GUI elements.
 
   // this method is executed by the mainthread and can therefore access all GUI elements.
Line 158: Line 154:
 
     end;
 
     end;
 
   end;
 
   end;
 
+
</syntaxhighlight>
 +
<syntaxhighlight lang=pascal>
 
   procedure TMyThread.Execute;
 
   procedure TMyThread.Execute;
 
   var
 
   var
Line 178: Line 175:
 
       end;
 
       end;
 
   end;
 
   end;
 
+
</syntaxhighlight>
On the application,
+
Voici l'application :
 
+
<syntaxhighlight lang=pascal>
 
   Type
 
   Type
 
     TForm1 = class(TForm)
 
     TForm1 = class(TForm)
Line 194: Line 191:
 
       { public declarations }
 
       { public declarations }
 
     end;
 
     end;
 
+
</syntaxhighlight>
 +
<syntaxhighlight lang=pascal>
 
   procedure TForm1.FormCreate(Sender: TObject);
 
   procedure TForm1.FormCreate(Sender: TObject);
 
   begin
 
   begin
Line 201: Line 199:
 
     MyThread.OnShowStatus := @ShowStatus;
 
     MyThread.OnShowStatus := @ShowStatus;
 
   end;
 
   end;
 
+
</syntaxhighlight>
 +
<syntaxhighlight lang=pascal>
 
   procedure TForm1.FormDestroy(Sender: TObject);
 
   procedure TForm1.FormDestroy(Sender: TObject);
 
   begin
 
   begin
Line 208: Line 207:
 
     inherited;
 
     inherited;
 
   end;
 
   end;
 
+
</syntaxhighlight>
 +
<syntaxhighlight lang=pascal>
 
   procedure TForm1.Button1Click(Sender: TObject);
 
   procedure TForm1.Button1Click(Sender: TObject);
 
   begin
 
   begin
   MyThread.Resume;
+
   MyThread.Start;
 
   end;
 
   end;
 
+
</syntaxhighlight>
 +
<syntaxhighlight lang=pascal>
 
   procedure TForm1.ShowStatus(Status: string);
 
   procedure TForm1.ShowStatus(Status: string);
 
   begin
 
   begin
 
     Label1.Caption := Status;
 
     Label1.Caption := Status;
 
   end;
 
   end;
 +
</syntaxhighlight>
  
= Special things to take care of =
+
= Choses spéciales dont il faut s'occuper  =
There is a potential headache in Windows with Threads if you use the -Ct (stack check) switch.
+
Il y a une prise de tête potentielle dans Windows avec les Threads si vous employez l'option -Ct (contrôle de pile ).
For reasons not so clear the stack check will "trigger" on any TThread.Create if you use the default stack size.
+
Pour des raisons obscures le contrôle de pile se "déclenchera" sur un quelconque TThread.Create si vous employez la taille de pile par défaut.
The only work-around for the moment is to simply not use -Ct switch. Note that it does NOT cause an exception in
+
Le seul contournement(à ce problème) pour le moment est simplement de ne pas utiliser l'option -Ct. Noter qu'il ne cause pas d'exception dans la tâche principale, mais dans celle nouvellement crée. Ainsi, tout se passe comme si la tâche n'avait jamais commencée.
the main thread, but in the newly created one. This "looks" like if the thread was never started.
 
 
 
A good code to check for this and other exceptions which can occur in thread creation is:
 
  
 +
Voici un code correct permettant de vérifier les exceptions qui peuvent se produire dans la création d'une tâche:
  
 +
<syntaxhighlight lang=pascal>
 
     MyThread:=TThread.Create(False);
 
     MyThread:=TThread.Create(False);
 
     if Assigned(MyThread.FatalException) then
 
     if Assigned(MyThread.FatalException) then
 
       raise MyThread.FatalException;
 
       raise MyThread.FatalException;
 +
</syntaxhighlight>
  
 +
Ce code assure que toute exception qui se produit pendant la création d'une tâche soit relevée (et traitée) dans votre tâche principale.
  
This code will asure that any exception which occured during thread creation will be raised in your main thread.
+
= Les unités nécessaires pour une application multi-tâches =
 
+
Vous n'avez besoin d'aucune unité spéciale pour ceci pour travailler avec Windows .
= Units needed for a multithreaded application =
+
Cependant avec Linux, MacOSX et FreeBSD, vous avez besoin de l'unité cthreads et elle '''doit''' être la première untité utilisée du projet  (l'unité du programme , .lpr)!
You don´t need any special unit for this to work with Windows.
 
However with Linux, MacOSX and FreeBSD, you need the cthreads unit and it ''must'' be the first used unit of the project (the program unit, .lpr)!
 
 
 
So, your Lazarus application code should look like:
 
  
 +
Ainsi, votre code d'application Lazarus devrait resssembler à :
 +
<syntaxhighlight lang=pascal>
 
   program MyMultiThreadedProgram;
 
   program MyMultiThreadedProgram;
 
   {$mode objfpc}{$H+}
 
   {$mode objfpc}{$H+}
Line 250: Line 251:
 
     Forms
 
     Forms
 
     { add your units here },
 
     { add your units here },
 +
</syntaxhighlight>
 +
Si vous oubliez ceci vous obtiendrez cette erreur au démarrage :
 +
  Le fichier binaire n'a aucun support de tâches compilé en lui.
 +
  Recompiler l'application avec un gestionnaire de thread dans la clause uses du programme avant d'autres unités utilisant
 +
  les tâches. (Pour le message en anglais voir version anglaise de cette page)
  
If you forget this you will get this error on startup:
+
= Support SMP =
  This binary has no thread support compiled in.
+
La bonne nouvelle est que votre application travaille en multi-tâches correctement de cette façon, elle est déjà prête pour le SMP!
  Recompile the application with a thread-driver in the program uses clause before other units using thread.
 
  
= SMP Support =
+
= Deboguer les application multi-tâches avec  Lazarus =
The good news is that if your application works properly multithreaded this way, it is already SMP enabled!
+
Le deboguage sur Lazarus n'est pas encore entièrement fonctionnel.
  
= Debuging Multithreaded Applications with Lazarus =
+
== Deboguer la sortie ==
The debugging on Lazarus is not fully functional yet.
 
  
== Debugging output ==
+
Dans une application a une seule tâche, vous pouvez simplement écrire sur la console/le terminal/n'importe quoi et l'ordre des lignes est le même avec lesquelles elles ont été écrites .
 +
Dans une application multi-tâche les choses sont plus compliquées. Si deux tâches écrivent, c'est à dire qu'une ligne est écrite par la tâche A avant une ligne par la tâche B, alors les lignes ne sont pas neccessairement écrites dans cet ordre. Il peut même se produire qu'une tâche écrive sa propre sortie pendant qu'une autre tâche soit déjà en train d'écrire sur la sortie, résultant en des lignes qui se mélangent.
  
In a single threaded application, you can simply write to console/terminal/whatever and the order of the lines is the same as they were written.
+
L'unité LCLProc contient plusieurs fonctions , pour laisser chaque tâche écrire à son propre fichier journal:
In multithreaded application things are more complicated. If two threads are writing, say a line is written by thread A before a line by thread B, then the lines are not neccessarily written in that order. It can even happen, that a thread writes its output, while the other thread is writing a line.
+
<syntaxhighlight lang=pascal>
 
 
The LCLProc unit contains several functions, to let each thread write to its own log file:
 
 
   procedure DbgOutThreadLog(const Msg: string); overload;
 
   procedure DbgOutThreadLog(const Msg: string); overload;
 
   procedure DebuglnThreadLog(const Msg: string); overload;
 
   procedure DebuglnThreadLog(const Msg: string); overload;
 
   procedure DebuglnThreadLog(Args: array of const); overload;
 
   procedure DebuglnThreadLog(Args: array of const); overload;
 
   procedure DebuglnThreadLog; overload;
 
   procedure DebuglnThreadLog; overload;
 
+
</syntaxhighlight>
For example:
+
Par exemple :
Instead of ''writeln('Some text ',123);'' use
+
Au lieu de  ''writeln('Some text ',123);'' utilisez
 +
<syntaxhighlight lang=pascal>
 
   DebuglnThreadLog(['Some text ',123]);
 
   DebuglnThreadLog(['Some text ',123]);
 +
</syntaxhighlight>
 +
Ceci ajoutera une ligne  'Some text 123' à '''Log<PID>.txt''', où  <PID> est l'ID du processus de la tâche courante.
  
This will append a line 'Some text 123' to '''Log<PID>.txt''', where <PID> is the process ID of the current thread.
+
C'est une bonne idée d'enlever le fichier journal avant chaque exécution :
 
 
It is a good idea to remove the log files before each run:
 
 
   rm -f Log* && ./project1
 
   rm -f Log* && ./project1
  
 
== Linux ==
 
== Linux ==
If you try to debug a multithreaded application on Linux, you will have one big problem: the X server will hang.
+
Si vous essayez de deboguer une application multi-tâches sous Linux, vous aurez un grand problème : le serveur X va crasher !
  
It is unknown how to solve this properly, but a workaround is:
+
On ne sait pas comment résoudre cela proprement, mais un contournement de ce problème est:
  
Create a new instance of X with:
+
Créer une nouvelle instance de X avec :
  
 
   X :1 &
 
   X :1 &
  
It will open, and when you switch to another desktop (the one you are working with pressing CTRL+ALT+F7), you will be able to go back to the new graphical desktop with CTRL+ALT+F8 (if this combination does not work, try with CTRL+ALT+F2... this one worked on Slackware).
+
Il s'ouvrira, et quand vous commuterez vers un autre bureau (celui avec lequel vous travaillez en appuyant sur CTRL+ALT+F7), vous pourrez retourner  vers le nouveau bureau graphique avec CTRL+ALT+F8 (si cette combinaison ne fonctionne pas , essayez avec  CTRL+ALT+F2... celui-ci a fonctionné avec Slackware).
  
Then you could, if you want, create a desktop session on the X started with:
+
Alors vous pourriez , si vous voulez , créer une session bureau au démarrage de X avec:
  
 
   gnome-session --display=:1 &
 
   gnome-session --display=:1 &
  
Then, in Lazarus, on the run parameters dialog for the project, check "Use display" and enter :1.
+
Puis, dans Lazarus, avec la boite de dialogue "paramètres d'exécution..." du menu Exécuter, cocher "Utiliser l'affichage" et entrez :1.
 +
 
 +
Maintenant l'application s'exécutera sur le deuxième serveur X et vous serez capable de la déboguer avec le premier.
 +
 
 +
Ceci a été testé avec Free Pascal 2.0 et Lazarus 0.9.10 sur  Windows et Linux.
 +
 
  
Now the application will run on the seccond X server and you will be able to debug it on the first one.
+
----
  
This was tested with Free Pascal 2.0 and Lazarus 0.9.10 on Windows and Linux.
+
Au lieu de créer une nouvelle session X, on peut employer  [http://en.wikipedia.org/wiki/Xnest Xnest]. Xnest est une session X session sur une fenêtre . En l'utilisant, le serveur X n'est pas bloqué pendant le debogage des processus, et c'est beaucoup plus facile de deboguer sans être tenu de changer de terminals.
 +
 
 +
La ligne de commande pour exécuter Xnest est
 +
 
 +
  Xnest :1 -ac
 +
 
 +
pour créer une session X sur :1, et interdire de contrôle d'accès.
  
 
= Widgetsets =
 
= Widgetsets =
  
The win32, the gtk and the carbon interfaces fully support multithreading. This means, TThread, critical sections and Synchronize work.
+
Les interfaces win32, gtk et carbon supportent entièrement le multi-tâches. Ceci signifie que, TThread, les sections critiques et Synchronize fonctionnent.
  
= Critical sections =
+
= Les sections critiques  =
  
A ''critical section'' is an object used to make sure, that some part of the code is executed only by one thread at a time.
+
Une ''section critique'' est un objet employé pour s'assurer, qu'une certaine partie du code est exécutée seulement par une tâche à la fois.
A critical section needs to be created/initialized before it can be used and be freed when it is not needed anymore.
+
Une section critique doit être créée /initialisée avant qu'elle puisse être employée  et être libérée quand elle n'est plus nécessaire.
  
Critical sections are normally used this way:
+
Les sections critiques sont normalement employées de cette façon :
  
Add the unit SyncObjs.
+
Ajouter l'unité SyncObjs.
  
Declare the section (globally for all threads which should access the section):
+
Déclarer la section (de manière globale pour toutes les tâches qui devraient accéder à la section):
 +
<syntaxhighlight lang=pascal>
 
   MyCriticalSection: TRTLCriticalSection;
 
   MyCriticalSection: TRTLCriticalSection;
 
+
</syntaxhighlight>
Create the section:
+
Créer la section :
 +
<syntaxhighlight lang=pascal>
 
   InitializeCriticalSection(MyCriticalSection);
 
   InitializeCriticalSection(MyCriticalSection);
 
+
</syntaxhighlight>
Run some threads. Doing something exclusively
+
Executer quelques tâches. En faisant quelque chose exclusivement
 +
<syntaxhighlight lang=pascal>
 
   EnterCriticalSection(MyCriticalSection);
 
   EnterCriticalSection(MyCriticalSection);
 
   try
 
   try
     // access some variables, write files, send some network packets, etc
+
     // accéder à quelques variables , écrire les fichiers , envoyer quelques paquets de réseau , etc
 
   finally
 
   finally
 
     LeaveCriticalSection(MyCriticalSection);
 
     LeaveCriticalSection(MyCriticalSection);
 
   end;
 
   end;
 
+
</syntaxhighlight>
After all threads terminated, free it:
+
Après que toutes les tâches soient terminées , libérer la section critique :
 +
<syntaxhighlight lang=pascal>
 
   DeleteCriticalSection(MyCriticalSection);
 
   DeleteCriticalSection(MyCriticalSection);
 +
</syntaxhighlight>
 +
Comme alternative, vous pouvez employer un objet TCriticalSection. La création fait l'initialisation, la méthode Enter fait pénétrer dans EnterCriticalSection, la méthode Leave fait LeaveCriticalSection et la destruction de l'object fait la suppression.
  
As an alternative, you can use a TCriticalSection object. The creation does the initialization, the Enter method does the EnterCriticalSection, the Leave method does the LeaveCriticalSection and the destruction of the object does the deletion.
+
Par exemple : 5 tâches incrémentant un compteur.
 +
Voir  lazarus/examples/multithreading/criticalsectionexample1.lpi
  
For example: 5 threads incrementing a counter.
+
'''PRENDRE GARDE :''' Il y a deux jeux pour les 4 fonctions ci-dessus. Celui de la bibliothèque RTL et celui de la LCL. Celui de la LCL est défini dans les unités LCLIntf et LCLType. Tous les deux fonctionnent plus ou moins de la même façon . Vous pouvez les employer tous les deux en même temps dans votre application , mais vous ne devriez pas employer une fonction de la bibliothèque RTL avec une section critique LCL et vice-versa.
See lazarus/examples/multithreading/criticalsectionexample1.lpi
 
  
'''BEWARE:''' There are two sets of the above 4 functions. The RTL and the LCL ones. The LCL ones are defined in the unit LCLIntf and LCLType. Both work pretty much the same. You can use both at the same time in your application, but you should not use a RTL function with an LCL Critical Section and vice versus.
 
  
 +
== Partage de variables  ==
  
== Sharing Variables ==
+
Si certaines tâches partagent une variable, seulement en lecture, alors il n'y a pas à s'inquiéter. juste la lire.
 +
Mais si une ou plusieurs tâches modifient la variable, alors vous devez vous assurer , que seulement une tâche accède aux variables à la fois .
  
If some threads share a variable, that is read only, then there is nothing to worry about. Just read it.
+
Par exemple : 5 tâches incrémentant un compteur.
But if one or several threads changes the variable, then you must make sure, that only one thread accesses the variables at a time.
+
Voir lazarus/examples/multithreading/criticalsectionexample1.lpi
  
For example: 5 threads incrementing a counter.
+
= Attente une autre tâche =
See lazarus/examples/multithreading/criticalsectionexample1.lpi
 
  
= Waiting for another thread =
+
Si une tâche A a besoin du résultat d'une autre tâche B, elle doit attendre , jusqu'à ce que B ait fini .
  
If a thread A needs a result of another thread B, it must wait, till B has finished.  
+
'''Important:''' La tâche principale ne devrait jamais attendre pour une autre tâche. Au lieu de cela utilisez  Synchronize (voir ci-dessus).
  
'''Important:''' The main thread should never wait for another thread. Instead use Synchronize (see above).
+
Pour un exemple voir: lazarus/examples/multithreading/waitforexample1.lpi
  
See for an example: lazarus/examples/multithreading/waitforexample1.lpi
+
<syntaxhighlight lang=pascal>
 
 
<pre>
 
 
{ TThreadA }
 
{ TThreadA }
  
Line 386: Line 405:
 
   end;
 
   end;
 
end;
 
end;
</pre>
+
</syntaxhighlight>
 +
 
 +
= Fork =
 +
 
 +
Prenez garde au fait que lorsque vous forkez une application multi-threads, tous les threads créés et s'exécutant avant l'appel à fork (ou fpFork) ne seront pas exécutés dans le processus enfant. Comme indiqué dans la 'man page' de la fonction fork(), tous les threads qui sont en cours d'exécution avant l'appel à fork seront dans un état indéterminé.
 +
 
 +
Donc attention a toutes les thread s'initialisant avant l'appel d'un fork (même dans la section initialization ). Ils ne fonctionneront pas.
 +
 
 +
= procédures/boucles parallèles =
 +
 
 +
Un cas particulier de multi threading est exécute une procédure unique en parallèle. Voir [[Parallel procedures/fr|procédures Parallèles]].
 +
 
 +
= Traitement distribué / Distributed computing =
 +
 
 +
Le prochain grand pas après le 'multi-threading' est d'exécuter des threads sur plusieurs machines.
 +
* Vous pouvez utiliser une des suites TCP comme synapse, lnet ou indy pour la communication. Cela vous donnera le maximum de flexibilité et est surtout utilisé dans les applications client / serveur.
 +
* Vous pouvez utiliser une bibliothèque de passage de message comme [[MPICH]], utilisé sur le clusters pour 'HPC' (High Performance Computing).

Latest revision as of 00:30, 21 February 2020

Deutsch (de) English (en) español (es) français (fr) 日本語 (ja) polski (pl) português (pt) русский (ru) slovenčina (sk) 中文(中国大陆)‎ (zh_CN)

Vue d'ensemble

Cette page essayera d'expliquer comment écrire et corriger une application multi-tâches avec Free Pascal et Lazarus.

Une application multi-tâches est celle qui crée deux tâches d'execution ou plus en même temps.

Si vous êtes nouveau dans le multi-tâches, veuillez lire le paragraphe "Avez vous besoin du multi-tâches ?" pour découvrir si vous en avez vraiment besoin (Vous pouvez vous éviter beaucoup de maux de tête).

Une des tâches (threads) s'appelle la tâche principale . La tâche principale est celle qui est créé par le système d'exploitation lorsque notre application démarre. La tâche principale doit être la seule tâche qui met à jour les composants qui interfèrent avec l'utilisateur (autrement , l'application risque de planter ).

L'idée principale est que l'application peut faire quelques traitements en arrière-plan (dans une deuxième tâche) tandis que l'utilisateur peut continuer à travailler (en utilisant la tâche principale).

L'utilisation des tâches permet aussi d'avoir une application qui répond mieux. Si vous créez une application, et que, quand l'utilisateur appuie sur un bouton, l'application commence le traitement (un gros travail )... pendant le traitement , L'écran arrête de répondre, et donne à l'utilisateur la sensation que l'application est plantée, ce n'est pas très agréable. Si le gros travail fonctionne dans une deuxième tâche, l'application continue à répondre (presque ) comme si elle était en idle. Dans ce cas, il est préférable avant de commencer la tâche, de neutraliser les boutons de la fiche pour éviter que l'utilisateur ne démarre plus d'une tâche de travail .

Une autre utilisation , est de créer une application serveur qui est capable de répondre à beaucoup de clients en même temps .

Avez vous besoin du multi-tâche?

Si vous êtes un novice dans le multi-tâche et que vous souhaitez simplement rendre votre application plus nerveuse lorsqu'elle a une grande charge de travail, alors le multi-tâche n'est peut-être pas ce que vous recherchez. Les applications multi-tâches sont toujours plus difficile et à debogguer, elles sont souvent beaucoup plus complexes. Dans beaucoup de cas vous n'avez pas besoin du multi-tâche, une seule tâche est suffisante. Si vous pouvez fractionner les longues corvées de travail en plusieurs petits morceaux, utilisez plutôt Application.ProcessMessages, cette méthode laisse la LCL traiter tous les messages en attente et en retour. L'idée est de traiter une partie du travail, puis d'appeller Application.ProcessMessages pour voir si l'utilisateur abandonne ou clique quelque part, ou d'actualiser un indicateur de progression, et ensuite de continuer avec la partie suivante du travail, appeler Application.ProcessMessages et ainsi de suite. Pensez également, lors de l'exécution d'une application externe (à la manière d'un script) de laisser du temps à l'éventuel système tiers de digérer la commande en mettant votre application en pause à l'aide de la commande sleep(miliseconds) de l'unité sysutils.

Par exemple : Lecture d'un grand fichier et son traitement . Voir examples/multithreading/singlethreadingexample1.lpi.

Le multi-tâche est seulement nécessaire pour

  • le blocage des handles, comme les communications de réseau
  • l'utilisation de plusieurs processeurs à la fois
  • les algorithmes et les appels de bibliothèque, qui ne peuvent pas être fractionné dans de petites pièces.

La classe TThread

L'exemple suivant se trouve dans le répertoire examples/multithreading/.

Pour créer une application multi-tâches, la manière la plus simple est d'utiliser la classe TThread.

Cette classe permet la création d'une tâche supplémentaire (à côté de la tâche principale) de manière simple.

Normalement, il suffit de surcharger 2 méthodes: le constructeur Create, et la méthode Execute.

Dans le constructeur, vous préparez la tâche à s'exécuter, vous fixerez les valeurs initiales des variables ou des propriétés que vous souhaitez. Le constructeur original de TThread exige un paramètre appelé Suspended. Comme vous pourriez le prévoir, fixer Suspended = True empêchera la tâche de commencer automatiquement après sa création . Si Suspended = False, la tâche commencera à fonctionner juste après sa création, si Suspended = True elle ne fonctionnera qu'après l'appel de la méthode Start.

Light bulb  Remarque: La méthode Resume est obsolète depuis FPC 2.4.4. Elle est remplacée par Start.

A partir de FPC version 2.0.1 et au delà, TThread.Create a également un paramètre implicite de taille de la pile. Vous pouvez maintenant changer la taille de la pile par défaut pour chaque tâche que vous créez (si vous en avez besoin). Les appels de procedure récursifs dans une tâche sont un bon exemple. Si vous n'indiquez pas le paramètre de la taille de la pile, la taille de pile de l'OS par défaut est employée.

Dans la méthode surchargée Execute vous écrivez le code qui fonctionnera dans la tâche.

La classe TThread a une propriété importante : Terminated : boolean;

Si la tâche est une boucle (ce qui est habituel), la boucle devra être quittée quand Terminated passera à True (sa valeur est False par défaut). Ainsi dans chaque cycle, la tâche doit tester si Terminated vaut True, et si c'est le cas, doit sortir de la méthode Execute aussi rapidement que possible (après tout nettoyage nécessaire).

Gardez aussi à l'esprit que la méthode Terminate ne fait rien par défaut: la méthode Execute doit explicitement faire le nécessaire pour stopper son travail.

Comme nous l'avons expliqué plus tôt, la tâche ne devra pas interagir avec les composants visuels. Pour interagir avec l'utilisateur elle devra le faire dans la tâche principale. Pour faire cela, il faut utiliser la méthode Synchronize de la classe TThread. Synchronize prend en paramètre une méthode (qui, elle, ne prend aucun paramètre) comme argument. Quand vous appelez cette méthode au moyen de Synchronize(@MyMethod), l'execution de la tâche sera mis en pause, le code de MyMethod s'exécutera dans la tâche principale, et ensuite l'exécution de la tâche reprendra. Le fonctionnement exact de Synchronize dépend de la plate-forme, mais fondamentalement elle fait ceci: Elle se signale par un message sur la file d'attente de message principale et va dormir. Par la suite, lorsque la tâche principale traite ce message, elle appelle MyMethod. De cette façon MyMethod est appelé hors contexte, ce qui signifie pas pendant un évènement de déplacement de souris ou pendant un événement de peinture, mais après. Dès que la tâche principale a exécuté MyMethod, elle réveille la tâche en sommeil et exécute le message suivant. La tâche continue alors.

Il y a une autre propriété importante de la classe TThread: FreeOnTerminate. Si cette propriété est true, l'objet tâche est automatiquement libéré quand l'execution de la tâche (méthode .Execute) s'arrête, sinon l'application devra le libérer manuellement.

Exemple :

  Type
    TMyThread = class(TThread)
    private
      fStatusText : string;
      procedure ShowStatus;
    protected
      procedure Execute; override;
    public
      Constructor Create(CreateSuspended : boolean);
    end;
  constructor TMyThread.Create(CreateSuspended : boolean);
  begin
    FreeOnTerminate := True;
    inherited Create(CreateSuspended);
  end;
  procedure TMyThread.ShowStatus;
  // this method is executed by the mainthread and can therefore access all GUI elements.
  begin
    Form1.Caption := fStatusText;
  end;
  procedure TMyThread.Execute;
  var
    newStatus : string;
  begin
    fStatusText := 'TMyThread Starting...';
    Synchronize(@Showstatus);
    fStatusText := 'TMyThread Running...';
    while (not Terminated) and ([any condition required]) do
      begin
        ...
        [here goes the code of the main thread loop]
        ...
        if NewStatus <> fStatusText then
          begin
            fStatusText := newStatus;
            Synchronize(@Showstatus);
          end;
      end;
  end;

Voici l'application :

  var
    MyThread : TMyThread;
  begin
    MyThread := TMyThread.Create(True); // This way it doesn't start automatically
    ...
    [Here the code initialises anything required before the threads starts executing]
    ...
    MyThread.Start;
  end;

Si vous voulez rendre votre synchronisation plus flexible vous pouvez créer un événement pour cette tâche - de cette façon votre méthode synchronized ne sera pas étroitement couplée avec une fiche spécifique ou une classe - il suffit ensuite d'avoir une méthode répondant à l'évènement du thread. Voici un exemple :

  Type
    TShowStatusEvent = procedure(Status: String) of Object;

    TMyThread = class(TThread)
    private
      fStatusText : string;
      FOnShowStatus: TShowStatusEvent;
      procedure ShowStatus;
    protected
      procedure Execute; override;
    public
      Constructor Create(CreateSuspended : boolean);
      property OnShowStatus: TShowStatusEvent read FOnShowStatus write FOnShowStatus;
    end;
  constructor TMyThread.Create(CreateSuspended : boolean);
  begin
    FreeOnTerminate := True;
    inherited Create(CreateSuspended);
  end;
  procedure TMyThread.ShowStatus;
  // this method is executed by the mainthread and can therefore access all GUI elements.
  begin
    if Assigned(FOnShowStatus) then
    begin
      FOnShowStatus(fStatusText);
    end;
  end;
  procedure TMyThread.Execute;
  var
    newStatus : string;
  begin
    fStatusText := 'TMyThread Starting...';
    Synchronize(@Showstatus);
    fStatusText := 'TMyThread Running...';
    while (not Terminated) and ([any condition required]) do
      begin
        ...
        [here goes the code of the main thread loop]
        ...
        if NewStatus <> fStatusText then
          begin
            fStatusText := newStatus;
            Synchronize(@Showstatus);
          end;
      end;
  end;

Voici l'application :

  Type
    TForm1 = class(TForm)
      Button1: TButton;
      Label1: TLabel;
      procedure FormCreate(Sender: TObject);
      procedure FormDestroy(Sender: TObject);
    private
      { private declarations }
      MyThread: TMyThread; 
      procedure ShowStatus(Status: string);
    public
      { public declarations }
    end;
  procedure TForm1.FormCreate(Sender: TObject);
  begin
    inherited;
    MyThread := TMyThread.Create(true);
    MyThread.OnShowStatus := @ShowStatus;
  end;
  procedure TForm1.FormDestroy(Sender: TObject);
  begin
    MyThread.Terminate;
    MyThread.Free;
    inherited;
  end;
  procedure TForm1.Button1Click(Sender: TObject);
  begin
   MyThread.Start;
  end;
  procedure TForm1.ShowStatus(Status: string);
  begin
    Label1.Caption := Status;
  end;

Choses spéciales dont il faut s'occuper

Il y a une prise de tête potentielle dans Windows avec les Threads si vous employez l'option -Ct (contrôle de pile ). Pour des raisons obscures le contrôle de pile se "déclenchera" sur un quelconque TThread.Create si vous employez la taille de pile par défaut. Le seul contournement(à ce problème) pour le moment est simplement de ne pas utiliser l'option -Ct. Noter qu'il ne cause pas d'exception dans la tâche principale, mais dans celle nouvellement crée. Ainsi, tout se passe comme si la tâche n'avait jamais commencée.

Voici un code correct permettant de vérifier les exceptions qui peuvent se produire dans la création d'une tâche:

     MyThread:=TThread.Create(False);
     if Assigned(MyThread.FatalException) then
       raise MyThread.FatalException;

Ce code assure que toute exception qui se produit pendant la création d'une tâche soit relevée (et traitée) dans votre tâche principale.

Les unités nécessaires pour une application multi-tâches

Vous n'avez besoin d'aucune unité spéciale pour ceci pour travailler avec Windows . Cependant avec Linux, MacOSX et FreeBSD, vous avez besoin de l'unité cthreads et elle doit être la première untité utilisée du projet (l'unité du programme , .lpr)!

Ainsi, votre code d'application Lazarus devrait resssembler à :

  program MyMultiThreadedProgram;
  {$mode objfpc}{$H+}
  uses
  {$ifdef unix}
    cthreads,
  {$endif}
    Interfaces, // this includes the LCL widgetset
    Forms
    { add your units here },

Si vous oubliez ceci vous obtiendrez cette erreur au démarrage :

 Le fichier binaire n'a aucun support de tâches compilé en lui.
 Recompiler l'application avec un gestionnaire de thread dans la clause uses du programme avant d'autres unités utilisant
 les tâches. (Pour le message en anglais voir version anglaise de cette page)

Support SMP

La bonne nouvelle est que votre application travaille en multi-tâches correctement de cette façon, elle est déjà prête pour le SMP!

Deboguer les application multi-tâches avec Lazarus

Le deboguage sur Lazarus n'est pas encore entièrement fonctionnel.

Deboguer la sortie

Dans une application a une seule tâche, vous pouvez simplement écrire sur la console/le terminal/n'importe quoi et l'ordre des lignes est le même avec lesquelles elles ont été écrites . Dans une application multi-tâche les choses sont plus compliquées. Si deux tâches écrivent, c'est à dire qu'une ligne est écrite par la tâche A avant une ligne par la tâche B, alors les lignes ne sont pas neccessairement écrites dans cet ordre. Il peut même se produire qu'une tâche écrive sa propre sortie pendant qu'une autre tâche soit déjà en train d'écrire sur la sortie, résultant en des lignes qui se mélangent.

L'unité LCLProc contient plusieurs fonctions , pour laisser chaque tâche écrire à son propre fichier journal:

  procedure DbgOutThreadLog(const Msg: string); overload;
  procedure DebuglnThreadLog(const Msg: string); overload;
  procedure DebuglnThreadLog(Args: array of const); overload;
  procedure DebuglnThreadLog; overload;

Par exemple : Au lieu de writeln('Some text ',123); utilisez

 
  DebuglnThreadLog(['Some text ',123]);

Ceci ajoutera une ligne 'Some text 123' à Log<PID>.txt, où <PID> est l'ID du processus de la tâche courante.

C'est une bonne idée d'enlever le fichier journal avant chaque exécution :

 rm -f Log* && ./project1

Linux

Si vous essayez de deboguer une application multi-tâches sous Linux, vous aurez un grand problème : le serveur X va crasher !

On ne sait pas comment résoudre cela proprement, mais un contournement de ce problème est:

Créer une nouvelle instance de X avec :

 X :1 &

Il s'ouvrira, et quand vous commuterez vers un autre bureau (celui avec lequel vous travaillez en appuyant sur CTRL+ALT+F7), vous pourrez retourner vers le nouveau bureau graphique avec CTRL+ALT+F8 (si cette combinaison ne fonctionne pas , essayez avec CTRL+ALT+F2... celui-ci a fonctionné avec Slackware).

Alors vous pourriez , si vous voulez , créer une session bureau au démarrage de X avec:

 gnome-session --display=:1 &

Puis, dans Lazarus, avec la boite de dialogue "paramètres d'exécution..." du menu Exécuter, cocher "Utiliser l'affichage" et entrez :1.

Maintenant l'application s'exécutera sur le deuxième serveur X et vous serez capable de la déboguer avec le premier.

Ceci a été testé avec Free Pascal 2.0 et Lazarus 0.9.10 sur Windows et Linux.



Au lieu de créer une nouvelle session X, on peut employer Xnest. Xnest est une session X session sur une fenêtre . En l'utilisant, le serveur X n'est pas bloqué pendant le debogage des processus, et c'est beaucoup plus facile de deboguer sans être tenu de changer de terminals.

La ligne de commande pour exécuter Xnest est

 Xnest :1 -ac

pour créer une session X sur :1, et interdire de contrôle d'accès.

Widgetsets

Les interfaces win32, gtk et carbon supportent entièrement le multi-tâches. Ceci signifie que, TThread, les sections critiques et Synchronize fonctionnent.

Les sections critiques

Une section critique est un objet employé pour s'assurer, qu'une certaine partie du code est exécutée seulement par une tâche à la fois. Une section critique doit être créée /initialisée avant qu'elle puisse être employée et être libérée quand elle n'est plus nécessaire.

Les sections critiques sont normalement employées de cette façon :

Ajouter l'unité SyncObjs.

Déclarer la section (de manière globale pour toutes les tâches qui devraient accéder à la section):

  MyCriticalSection: TRTLCriticalSection;

Créer la section :

  InitializeCriticalSection(MyCriticalSection);

Executer quelques tâches. En faisant quelque chose exclusivement

  EnterCriticalSection(MyCriticalSection);
  try
    // accéder à quelques variables , écrire les fichiers , envoyer quelques paquets de réseau , etc
  finally
    LeaveCriticalSection(MyCriticalSection);
  end;

Après que toutes les tâches soient terminées , libérer la section critique :

  DeleteCriticalSection(MyCriticalSection);

Comme alternative, vous pouvez employer un objet TCriticalSection. La création fait l'initialisation, la méthode Enter fait pénétrer dans EnterCriticalSection, la méthode Leave fait LeaveCriticalSection et la destruction de l'object fait la suppression.

Par exemple : 5 tâches incrémentant un compteur. Voir lazarus/examples/multithreading/criticalsectionexample1.lpi

PRENDRE GARDE : Il y a deux jeux pour les 4 fonctions ci-dessus. Celui de la bibliothèque RTL et celui de la LCL. Celui de la LCL est défini dans les unités LCLIntf et LCLType. Tous les deux fonctionnent plus ou moins de la même façon . Vous pouvez les employer tous les deux en même temps dans votre application , mais vous ne devriez pas employer une fonction de la bibliothèque RTL avec une section critique LCL et vice-versa.


Partage de variables

Si certaines tâches partagent une variable, seulement en lecture, alors il n'y a pas à s'inquiéter. juste la lire. Mais si une ou plusieurs tâches modifient la variable, alors vous devez vous assurer , que seulement une tâche accède aux variables à la fois .

Par exemple : 5 tâches incrémentant un compteur. Voir lazarus/examples/multithreading/criticalsectionexample1.lpi

Attente une autre tâche

Si une tâche A a besoin du résultat d'une autre tâche B, elle doit attendre , jusqu'à ce que B ait fini .

Important: La tâche principale ne devrait jamais attendre pour une autre tâche. Au lieu de cela utilisez Synchronize (voir ci-dessus).

Pour un exemple voir: lazarus/examples/multithreading/waitforexample1.lpi

{ TThreadA }

procedure TThreadA.Execute;
begin
  Form1.ThreadB:=TThreadB.Create(false);
  // create event
  WaitForB:=RTLEventCreate;
  while not Application.Terminated do begin
    // wait infinitely (until B wakes A)
    RtlEventWaitFor(WaitForB);
    writeln('A: ThreadB.Counter='+IntToStr(Form1.ThreadB.Counter));
  end;
end;

{ TThreadB }

procedure TThreadB.Execute;
var
  i: Integer;
begin
  Counter:=0;
  while not Application.Terminated do begin
    // B: Working ...
    Sleep(1500);
    inc(Counter);
    // wake A
    RtlEventSetEvent(Form1.ThreadA.WaitForB);
  end;
end;

Fork

Prenez garde au fait que lorsque vous forkez une application multi-threads, tous les threads créés et s'exécutant avant l'appel à fork (ou fpFork) ne seront pas exécutés dans le processus enfant. Comme indiqué dans la 'man page' de la fonction fork(), tous les threads qui sont en cours d'exécution avant l'appel à fork seront dans un état indéterminé.

Donc attention a toutes les thread s'initialisant avant l'appel d'un fork (même dans la section initialization ). Ils ne fonctionneront pas.

procédures/boucles parallèles

Un cas particulier de multi threading est exécute une procédure unique en parallèle. Voir procédures Parallèles.

Traitement distribué / Distributed computing

Le prochain grand pas après le 'multi-threading' est d'exécuter des threads sur plusieurs machines.

  • Vous pouvez utiliser une des suites TCP comme synapse, lnet ou indy pour la communication. Cela vous donnera le maximum de flexibilité et est surtout utilisé dans les applications client / serveur.
  • Vous pouvez utiliser une bibliothèque de passage de message comme MPICH, utilisé sur le clusters pour 'HPC' (High Performance Computing).