Sign in Join
Wiki copytemporary

Multithreaded Application Tutorial/fr: what changed

Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11# Vue d'ensemble
22
33Cette page essayera d'expliquer comment écrire et corriger une application multi-tâches avec Free Pascal et Lazarus.
44
55Une application multi-tâches est celle qui crée deux tâches d'execution ou plus en même temps.
66
77Si 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).
88
99Une 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 ).
1010
1111L'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).
1212
1313L'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 .
1414
1515Une autre utilisation , est de créer une application serveur qui est capable de répondre à beaucoup de clients en même temps .
1616
1717# Avez vous besoin du multi-tâche?
1818
1919Si 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.
2020
2121Par exemple : Lecture d'un grand fichier et son traitement . Voir examples/multithreading/singlethreadingexample1.lpi.
2222
2323Le multi-tâche est seulement nécessaire pour
2424
2525- le blocage des handles, comme les communications de réseau
2626- l'utilisation de plusieurs processeurs à la fois
2727- les algorithmes et les appels de bibliothèque, qui ne peuvent pas être fractionné dans de petites pièces.
2828
2929# La classe TThread
3030
3131L'exemple suivant se trouve dans le répertoire examples/multithreading/.
3232
3333Pour créer une application multi-tâches, la manière la plus simple est d'utiliser la classe TThread.
3434
3535Cette classe permet la création d'une tâche supplémentaire (à côté de la tâche principale) de manière simple.
3636
3737Normalement, il suffit de surcharger 2 méthodes: le constructeur Create, et la méthode Execute.
3838
3939Dans 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.
4040
4141> **Note:** La méthode Resume est [obsolète depuis FPC 2.4.4](http://wiki.freepascal.org/User_Changes_2.4.4#TThread.Suspend_and_TThread.Resume_have_been_deprecated). Elle est remplacée par Start.
4242
4343A 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.
4444
4545Dans la méthode surchargée Execute vous écrivez le code qui fonctionnera dans la tâche.
4646
4747La classe TThread a une propriété importante : Terminated : boolean;
4848
4949Si 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).
5050
5151Gardez 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.
5252
5353Comme 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.
5454
5555Il 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.
5656
5757Exemple :
5858
5959``` pascal
6060 Type
6161 TMyThread = class(TThread)
6262 private
6363 fStatusText : string;
6464 procedure ShowStatus;
6565 protected
6666 procedure Execute; override;
6767 public
6868 Constructor Create(CreateSuspended : boolean);
6969 end;
7070```
7171
7272``` pascal
7373 constructor TMyThread.Create(CreateSuspended : boolean);
7474 begin
7575 FreeOnTerminate := True;
7676 inherited Create(CreateSuspended);
7777 end;
7878```
7979
8080``` pascal
8181 procedure TMyThread.ShowStatus;
8282 // this method is executed by the mainthread and can therefore access all GUI elements.
8383 begin
8484 Form1.Caption := fStatusText;
8585 end;
8686```
8787
8888``` pascal
8989 procedure TMyThread.Execute;
9090 var
9191 newStatus : string;
9292 begin
9393 fStatusText := 'TMyThread Starting...';
9494 Synchronize(@Showstatus);
9595 fStatusText := 'TMyThread Running...';
9696 while (not Terminated) and ([any condition required]) do
9797 begin
9898 ...
9999 [here goes the code of the main thread loop]
100100 ...
101101 if NewStatus <> fStatusText then
102102 begin
103103 fStatusText := newStatus;
104104 Synchronize(@Showstatus);
105105 end;
106106 end;
107107 end;
108108```
109109
110110Voici l'application :
111111
112112``` pascal
113113 var
114114 MyThread : TMyThread;
115115 begin
116116 MyThread := TMyThread.Create(True); // This way it doesn't start automatically
117117 ...
118118 [Here the code initialises anything required before the threads starts executing]
119119 ...
120120 MyThread.Start;
121121 end;
122122```
123123
124124Si 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 :
125125
126126``` pascal
127127 Type
128128 TShowStatusEvent = procedure(Status: String) of Object;
129129
130130 TMyThread = class(TThread)
131131 private
132132 fStatusText : string;
133133 FOnShowStatus: TShowStatusEvent;
134134 procedure ShowStatus;
135135 protected
136136 procedure Execute; override;
137137 public
138138 Constructor Create(CreateSuspended : boolean);
139139 property OnShowStatus: TShowStatusEvent read FOnShowStatus write FOnShowStatus;
140140 end;
141141```
142142
143143``` pascal
144144 constructor TMyThread.Create(CreateSuspended : boolean);
145145 begin
146146 FreeOnTerminate := True;
147147 inherited Create(CreateSuspended);
148148 end;
149149```
150150
151151``` pascal
152152 procedure TMyThread.ShowStatus;
153153 // this method is executed by the mainthread and can therefore access all GUI elements.
154154 begin
155155 if Assigned(FOnShowStatus) then
156156 begin
157157 FOnShowStatus(fStatusText);
158158 end;
159159 end;
160160```
161161
162162``` pascal
163163 procedure TMyThread.Execute;
164164 var
165165 newStatus : string;
166166 begin
167167 fStatusText := 'TMyThread Starting...';
168168 Synchronize(@Showstatus);
169169 fStatusText := 'TMyThread Running...';
170170 while (not Terminated) and ([any condition required]) do
171171 begin
172172 ...
173173 [here goes the code of the main thread loop]
174174 ...
175175 if NewStatus <> fStatusText then
176176 begin
177177 fStatusText := newStatus;
178178 Synchronize(@Showstatus);
179179 end;
180180 end;
181181 end;
182182```
183183
184184Voici l'application :
185185
186186``` pascal
187187 Type
188188 TForm1 = class(TForm)
189189 Button1: TButton;
190190 Label1: TLabel;
191191 procedure FormCreate(Sender: TObject);
192192 procedure FormDestroy(Sender: TObject);
193193 private
194194 { private declarations }
195195 MyThread: TMyThread;
196196 procedure ShowStatus(Status: string);
197197 public
198198 { public declarations }
199199 end;
200200```
201201
202202``` pascal
203203 procedure TForm1.FormCreate(Sender: TObject);
204204 begin
205205 inherited;
206206 MyThread := TMyThread.Create(true);
207207 MyThread.OnShowStatus := @ShowStatus;
208208 end;
209209```
210210
211211``` pascal
212212 procedure TForm1.FormDestroy(Sender: TObject);
213213 begin
214214 MyThread.Terminate;
215215 MyThread.Free;
216216 inherited;
217217 end;
218218```
219219
220220``` pascal
221221 procedure TForm1.Button1Click(Sender: TObject);
222222 begin
223223 MyThread.Start;
224224 end;
225225```
226226
227227``` pascal
228228 procedure TForm1.ShowStatus(Status: string);
229229 begin
230230 Label1.Caption := Status;
231231 end;
232232```
233233
234234# Choses spéciales dont il faut s'occuper
235235
236236Il 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.
237237
238238Voici un code correct permettant de vérifier les exceptions qui peuvent se produire dans la création d'une tâche:
239239
240240``` pascal
241241 MyThread:=TThread.Create(False);
242242 if Assigned(MyThread.FatalException) then
243243 raise MyThread.FatalException;
244244```
245245
246246Ce 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.
247247
248248# Les unités nécessaires pour une application multi-tâches
249249
250250Vous 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)!
251251
252252Ainsi, votre code d'application Lazarus devrait resssembler à :
253253
254254``` pascal
255255 program MyMultiThreadedProgram;
256256 {$mode objfpc}{$H+}
257257 uses
258258 {$ifdef unix}
259259 cthreads,
260260 {$endif}
261261 Interfaces, // this includes the LCL widgetset
262262 Forms
263263 { add your units here },
264264```
265265
266266Si vous oubliez ceci vous obtiendrez cette erreur au démarrage :
267267
268-` Le fichier binaire n'a aucun support de tâches compilé en lui.`\
269-` Recompiler l'application avec un gestionnaire de thread dans la clause uses du programme avant d'autres unités utilisant`\
270-` les tâches. (Pour le message en anglais voir version anglaise de cette page)`
268+``` pascal
269+ Le fichier binaire n'a aucun support de tâches compilé en lui.
270+ Recompiler l'application avec un gestionnaire de thread dans la clause uses du programme avant d'autres unités utilisant
271+ les tâches. (Pour le message en anglais voir version anglaise de cette page)
272+```
271273
272274# Support SMP
273275
274276La 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!
275277
276278# Deboguer les application multi-tâches avec Lazarus
277279
278280Le deboguage sur Lazarus n'est pas encore entièrement fonctionnel.
279281
280282## Deboguer la sortie
281283
282284Dans 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.
283285
284286L'unité LCLProc contient plusieurs fonctions , pour laisser chaque tâche écrire à son propre fichier journal:
285287
286288``` pascal
287289 procedure DbgOutThreadLog(const Msg: string); overload;
288290 procedure DebuglnThreadLog(const Msg: string); overload;
289291 procedure DebuglnThreadLog(Args: array of const); overload;
290292 procedure DebuglnThreadLog; overload;
291293```
292294
293295Par exemple : Au lieu de *writeln('Some text ',123);* utilisez
294296
295297``` pascal
296298
297299 DebuglnThreadLog(['Some text ',123]);
298300```
299301
300302Ceci ajoutera une ligne 'Some text 123' à **Log\<PID\>.txt**, où \<PID\> est l'ID du processus de la tâche courante.
301303
302304C'est une bonne idée d'enlever le fichier journal avant chaque exécution :
303305
304-` rm -f Log* && ./project1`
306+```text
307+ rm -f Log* && ./project1
308+```
305309
306310## Linux
307311
308312Si vous essayez de deboguer une application multi-tâches sous Linux, vous aurez un grand problème : le serveur X va crasher !
309313
310314On ne sait pas comment résoudre cela proprement, mais un contournement de ce problème est:
311315
312316Créer une nouvelle instance de X avec :
313317
314-` X :1 &`
318+```text
319+ X :1 &
320+```
315321
316322Il 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).
317323
318324Alors vous pourriez , si vous voulez , créer une session bureau au démarrage de X avec:
319325
320-` gnome-session --display=:1 &`
326+```text
327+ gnome-session --display=:1 &
328+```
321329
322330Puis, dans Lazarus, avec la boite de dialogue "paramètres d'exécution..." du menu Exécuter, cocher "Utiliser l'affichage" et entrez :1.
323331
324332Maintenant l'application s'exécutera sur le deuxième serveur X et vous serez capable de la déboguer avec le premier.
325333
326334Ceci a été testé avec Free Pascal 2.0 et Lazarus 0.9.10 sur Windows et Linux.
327335
328336------------------------------------------------------------------------
329337
330338Au lieu de créer une nouvelle session X, on peut employer [Xnest](http://en.wikipedia.org/wiki/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.
331339
332340La ligne de commande pour exécuter Xnest est
333341
334-` Xnest :1 -ac`
342+```text
343+ Xnest :1 -ac
344+```
335345
336346pour créer une session X sur :1, et interdire de contrôle d'accès.
337347
338348# Widgetsets
339349
340350Les interfaces win32, gtk et carbon supportent entièrement le multi-tâches. Ceci signifie que, TThread, les sections critiques et Synchronize fonctionnent.
341351
342352# Les sections critiques
343353
344354Une *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.
345355
346356Les sections critiques sont normalement employées de cette façon :
347357
348358Ajouter l'unité SyncObjs.
349359
350360Déclarer la section (de manière globale pour toutes les tâches qui devraient accéder à la section):
351361
352362``` pascal
353363 MyCriticalSection: TRTLCriticalSection;
354364```
355365
356366Créer la section :
357367
358368``` pascal
359369 InitializeCriticalSection(MyCriticalSection);
360370```
361371
362372Executer quelques tâches. En faisant quelque chose exclusivement
363373
364374``` pascal
365375 EnterCriticalSection(MyCriticalSection);
366376 try
367377 // accéder à quelques variables , écrire les fichiers , envoyer quelques paquets de réseau , etc
368378 finally
369379 LeaveCriticalSection(MyCriticalSection);
370380 end;
371381```
372382
373383Après que toutes les tâches soient terminées , libérer la section critique :
374384
375385``` pascal
376386 DeleteCriticalSection(MyCriticalSection);
377387```
378388
379389Comme 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.
380390
381391Par exemple : 5 tâches incrémentant un compteur. Voir lazarus/examples/multithreading/criticalsectionexample1.lpi
382392
383393**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.
384394
385395## Partage de variables
386396
387397Si 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 .
388398
389399Par exemple : 5 tâches incrémentant un compteur. Voir lazarus/examples/multithreading/criticalsectionexample1.lpi
390400
391401# Attente une autre tâche
392402
393403Si une tâche A a besoin du résultat d'une autre tâche B, elle doit attendre , jusqu'à ce que B ait fini .
394404
395405**Important:** La tâche principale ne devrait jamais attendre pour une autre tâche. Au lieu de cela utilisez Synchronize (voir ci-dessus).
396406
397407Pour un exemple voir: lazarus/examples/multithreading/waitforexample1.lpi
398408
399409``` pascal
400410{ TThreadA }
401411
402412procedure TThreadA.Execute;
403413begin
404414 Form1.ThreadB:=TThreadB.Create(false);
405415 // create event
406416 WaitForB:=RTLEventCreate;
407417 while not Application.Terminated do begin
408418 // wait infinitely (until B wakes A)
409419 RtlEventWaitFor(WaitForB);
410420 writeln('A: ThreadB.Counter='+IntToStr(Form1.ThreadB.Counter));
411421 end;
412422end;
413423
414424{ TThreadB }
415425
416426procedure TThreadB.Execute;
417427var
418428 i: Integer;
419429begin
420430 Counter:=0;
421431 while not Application.Terminated do begin
422432 // B: Working ...
423433 Sleep(1500);
424434 inc(Counter);
425435 // wake A
426436 RtlEventSetEvent(Form1.ThreadA.WaitForB);
427437 end;
428438end;
429439```
430440
431441# Fork
432442
433443Prenez 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é.
434444
435445Donc attention a toutes les thread s'initialisant avant l'appel d'un fork (même dans la section initialization ). Ils ne fonctionneront pas.
436446
437447# procédures/boucles parallèles
438448
439449Un cas particulier de multi threading est exécute une procédure unique en parallèle. Voir [[Parallel procedures/fr|procédures Parallèles]].
440450
441451# Traitement distribué / Distributed computing
442452
443453Le prochain grand pas après le 'multi-threading' est d'exécuter des threads sur plusieurs machines.
444454
445455- 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.
446456- Vous pouvez utiliser une bibliothèque de passage de message comme [[MPICH]], utilisé sur le clusters pour 'HPC' (High Performance Computing).
447457
448458---
449459*From the Free Pascal wiki, page "Multithreaded Application Tutorial/fr", as it stood on 2020-02-20 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history