Sign in Join
Wiki copytemporary

Multithreaded Application Tutorial/de: what changed

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

OldNewLine
1-\
21Zurück zu den [[Additional information/de|Zusätzlichen Informationen]].\
3-\
2+
43== Überblick ==
54
65Diese Seite soll zeigen, wie man unter FreePascal und Lazarus Multithread-Anwendungen erstellt und verwaltet. In einer Multithread-Anwendung lassen sich verschiedene Aufgaben auf mehrere Threads verteilen, die gleichzeitig ausgeführt werden können.
76
87Wenn Sie bisher keinerlei Erfahrungen zur Multithread-Programmierung gemacht haben, empfehlen wie Ihnen, sich zunächst den Artikel "Benötigt Ihre Anwendung wirklich Multithread-Eigenschaften?" sorgfältig durch zu lesen, da Multithread-Programmierung kein leichtes Unterfangen ist.
98
109Das Hauptziel der Multithread-Programmierung ist die Verfügbarkeit der Benutzeroberfläche eines Programms, während es im Hintergrund Berechnungen durchführt. Dies kann man erreichen, indem man die Berechnung in einen Thread außerhalb des sogenannten Main-Thread verlagert, welcher für die Aktualisierung der Benutzeroberfläche zuständig ist.
1110
1211Andere Anwendungen, bei denen Multithread-Programmierung zum Einsatz kommt, sind Server-Anwendungen, die mehrere Klienten gleichzeitig betreuen müssen.
1312
1413Multithread-Anwendungen ermöglichen auch die Aufteilungen der Lasten einer Berechnung auf mehrere Kerne einer Multi-Core-CPU.
1514
1615Wichtig: Der Main Thread wird beim Start Ihrer Anwendung vom Betriebssystem erstellt. Der Main Thread ist dabei der einzige Thread (und **muss** auch der einzige bleiben), der für die Aktualisierung der Komponenten der Benutzeroberfläche zuständig ist (Forms, etc.) (ansonsten hängt sich Ihre Anwendung auf).
1716
1817## Benötigt Ihre Anwendung wirklich Multithread-Eigenschaften?
1918
2019Wenn Multithread Programmierung für Sie Neuland ist und Sie lediglich eine bessere Reaktionsfähigkeit ihrer Anwendung während langer Berechnungen benötigen, dann ist Multithreading nicht unbedingt die einfachste Lösung. Multithreading Applikationen sind immer schwieriger zu debuggen und auch oft viel komplexer. Außerdem benötigen Sie in vielen Fällen kein Multithreading, um ihre Anwendung reaktionsfähig zu halten. Statt dessen können Sie ein **Application.ProcessMessages** in Ihre Berechnungen mit einbauen. Dieses verarbeitet alle anstehenden Nachrichten, die an Ihre Applikation gesendet wurden und Ihre Applikation reagiert auf Ereignisse. Sie können also einen Teil der Berechnung durchführen, dann Application.ProcessMessages aufrufen und die Benutzereingaben werden verarbeitet und die Oberfläche neu gezeichnet. Platzieren Sie das Application.Processmessages z.B. in Schleifen, so dass bei jedem Schleifendurchlauf die Benutzeroberfläche reagiert.
2120
2221Zum Beispiel: In den Lazarus-Beispielen unter examples/multithreading/singlethreadingexample1.lpi wird eine große Datei gelesen und verarbeitet und die oben genannte Technik benutzt.
2322
2423Multithreading wird wirklich benötigt für:
2524
2625- blocking handles, wie Netzwerkkommunikation, Interrupts (Linux)
2726- Mehrprozessor- oder Mehrkernbetrieb
2827- Algorithmen und Bibliotheksaufrufe, die nicht in mehrere kleine Stufen zerteilt werden können.
2928
3029## Die Klasse TThread
3130
3231Das folgende Beispiel ist im Verzeichnis examples/multithreading/ zu finden. Der einfachste Weg, um eine Multithread-Anwendung zu erstellen, ist die Verwendung der Klasse TThread. Über diese Klasse lässt sich, neben dem Main-Thread, ein zusätzlicher Thread in einfacher Weise erstellen. Unter normalen Umständen müssen dazu bloß zwei Methoden überschrieben (override) werden: Der Konstruktor Create und die Methode Execute. Im Konstruktor Create wird der Thread für die spätere Ausführung vorbereitet. Hier werden die dem Thread zugrunde liegenden Variablen initialisiert. Der originale Konstruktor des Threads enthält einen Parameter namens "Suspended": Damit der Thread nicht automatisch nach seiner Erstellung gestartet wird, ist es empfehlenswert, diesen Parameter auf "true" zu setzen. In diesem Fall können Sie den Thread zu einem späteren Zeitpunkt über die Methode "Resume" starten. Ist es dagegen erwünscht, dass der Thread direkt nach seiner Erstellung startet, setzen Sie Suspended auf "false".
3332
3433Ab der Version 2.0.1 des FreePascal-Compilers lässt sich auch die Größe des von einem Thread verwendeten Stacks über einen Parameter des Konstruktor TThread.Create einstellen. Das kann beispielsweise bei rekursiven Prozeduren oder Funktionen nützlich sein. Wird die Größe des Stack nicht explizit festgelegt, verwendet FPC die vom Betriebssystem festgelegte Standardgröße.
3534
3635In die (mit Parameter Override) überschriebene Methode Execute schreibt man den vom Thread auszuführenden Quelltext hinein.
3736
3837Die Klasse TThread besitzt die wichtige Eigenschaft (property): Terminated : boolean; (Standardeinstellung: Terminated=false)
3938
4039Der Sinn der Eigenschaft Terminated ist, dass sich ein Thread damit zu einem beliebigen Zeitpunkt abbrechen lässt. Jedoch muss dies von dem Programmierer in den Quelltext der Methode Execute eingearbeitet werden - die Methode Terminate greift also nicht direkt in den vom Programmierer vorgesehenen Programmablauf ein. Sollten sich in Ihrem Thread also Repeatschleifen oder andere sich wiederholende Stellen finden, ist es deshalb notwendig, dass Sie in jeder Schleife explizit prüfen, ob die Eigenschaft Terminated wahr oder falsch ist. Sollte Terminated=true sein, muss die Methode Execute (evtl. nach einer finalen Konfiguration) auf schnellstem Wege verlassen werden.
4140
4241Wie eingangs erwähnt, sollte der erstellte Thread in keinem Fall mit den sichtbaren Komponenten der Benutzeroberfläche agieren. Die Benutzeroberfläche verändern, Eingaben abfragen, etc. darf ausschließlich der Main-Thread.
4342
4443Um trotzdem aus einem Thread heraus auf die Benutzeroberfläche bzw. Variablen des Hauptprogrammes zuzugreifen, existiert die Methode Synchronize. Synchronize wird mit einer procedure als Parameter aufgerufen. Wenn Sie Synchronize(@MyMethod) aufrufen, wird der Thread gestoppt, der Code in MyMethod wird im Hauptthread ausgeführt, und dann die Bearbeitung des Threads fortgesetzt. Die exakte Arbeitsweise von Synchronize hängt von der Plattform ab.
4544
4645Eine andere wichtige Eigenschaft (property) von TThread ist FreeOnTerminate. Wenn diese Eigenschaft auf True gesetzt ist, wird der Thread automatisch freigegeben (wie ein Aufruf von .Free), wenn Execute beendet wird. Beispiel:
4746
4847``` pascal
4948 Type
5049 TMyThread = class(TThread)
5150 private
5251 fStatusText : string;
5352 procedure ShowStatus;
5453 protected
5554 procedure Execute; override;
5655 public
5756 Constructor Create(CreateSuspended : boolean);
5857 end;
5958
6059 constructor TMyThread.Create(CreateSuspended : boolean);
6160 begin
6261 FreeOnTerminate := True;
6362 inherited Create(CreateSuspended);
6463 end;
6564
6665 procedure TMyThread.ShowStatus;
6766 // Diese Methode wird vom MainThread ausgeführt und kann deshalb auf alle GUI-Elemente zugreifen
6867 begin
6968 Form1.Caption := fStatusText;
7069 end;
7170
7271 procedure TMyThread.Execute;
7372 var
7473 newStatus : string;
7574 begin
7675 fStatusText := 'TMyThread Starting...';
7776 Synchronize(@Showstatus);
7877 fStatusText := 'TMyThread Running...';
7978 while (not Terminated) and ([any condition required]) do
8079 begin
8180 ...
8281 // here goes the code of the main thread loop
8382 ...
8483 if NewStatus <> fStatusText then
8584 begin
8685 fStatusText := newStatus;
8786 Synchronize(@Showstatus);
8887 end;
8988 end;
9089 end;
9190```
9291
9392In der Hauptunit erfolgt dann folgender Aufruf
9493
9594``` pascal
9695 var
9796 MyThread : TMyThread;
9897 begin
9998 MyThread := TMyThread.Create(True); // So startet es nicht automatisch
10099 ...
101100 // Here the code initialises anything required before the threads starts executing
102101 ...
103102 MyThread.Resume;
104103 end;
105104```
106105
107106Soll Ihr Programm flexibler sein, können Sie ein Ereignis für den Thread erzeugen - Ihre synchronisierte Methode ist dann nicht eng an eine bestimmte Form oder Klasse gebunden und Listeners können mit dem Ereignis des Threads verbunden werden. Hier ein Beispiel:
108107
109108``` pascal
110109 Type
111110 TShowStatusEvent = procedure(Status: String) of Object;
112111
113112 TMyThread = class(TThread)
114113 private
115114 fStatusText : string;
116115 FOnShowStatus: TShowStatusEvent;
117116 procedure ShowStatus;
118117 protected
119118 procedure Execute; override;
120119 public
121120 Constructor Create(CreateSuspended : boolean);
122121 property OnShowStatus: TShowStatusEvent read FOnShowStatus write FOnShowStatus;
123122 end;
124123
125124 constructor TMyThread.Create(CreateSuspended : boolean);
126125 begin
127126 FreeOnTerminate := True;
128127 inherited Create(CreateSuspended);
129128 end;
130129
131130 procedure TMyThread.ShowStatus;
132131 // Diese Methode wird vom MainThread ausgeführt und kann deshalb auf alle GUI-Elemente zugreifen
133132 begin
134133 if Assigned(FOnShowStatus) then
135134 begin
136135 FOnShowStatus(fStatusText);
137136 end;
138137 end;
139138
140139 procedure TMyThread.Execute;
141140 var
142141 newStatus : string;
143142 begin
144143 fStatusText := 'TMyThread Starting...';
145144 Synchronize(@Showstatus);
146145 fStatusText := 'TMyThread Running...';
147146 while (not Terminated) and ([any condition required]) do
148147 begin
149148 ...
150149 [here goes the code of the main thread loop]
151150 ...
152151 if NewStatus <> fStatusText then
153152 begin
154153 fStatusText := newStatus;
155154 Synchronize(@Showstatus);
156155 end;
157156 end;
158157 end;
159158```
160159
161160Und im Hauptformular
162161
163162``` pascal
164163 Type
165164 TForm1 = class(TForm)
166165 Button1: TButton;
167166 Label1: TLabel;
168167 procedure FormCreate(Sender: TObject);
169168 procedure FormDestroy(Sender: TObject);
170169 private
171170 { private declarations }
172171 MyThread: TMyThread;
173172 procedure ShowStatus(Status: string);
174173 public
175174 { public declarations }
176175 end;
177176
178177 procedure TForm1.FormCreate(Sender: TObject);
179178 begin
180179 inherited;
181180 MyThread := TMyThread.Create(true);
182181 MyThread.OnShowStatus := @ShowStatus;
183182 end;
184183
185184 procedure TForm1.FormDestroy(Sender: TObject);
186185 begin
187186 MyThread.Terminate;
188187 MyThread.Free;
189188 inherited;
190189 end;
191190
192191 procedure TForm1.Button1Click(Sender: TObject);
193192 begin
194193 MyThread.Resume;
195194 end;
196195
197196 procedure TForm1.ShowStatus(Status: string);
198197 begin
199198 Label1.Caption := Status;
200199 end;
201200```
202201
203202## Worauf Sie achten sollten
204203
205204Wenn Sie den Compilerswitch -Ct (Stack Check) benutzen, kann das einige Probleme mit sich bringen (Originalübersetzung: Es könnte Ihnen Kopfschmerzen bereiten :)) Aus unbekannten Gründen "triggert" der Stack Check an jedem TThread.Create, wenn Sie die Standardgröße des Stacks benutzen. Die einzige Lösung ist zur Zeit, -Ct nicht zu benutzen.
206205
207206Um auf Exceptions im Thread zu prüfen, können Sie folgendermaßen vorgehen:
208207
209208``` pascal
210209 MyThread := TThread.Create(False);
211210 if Assigned(MyThread.FatalException) then
212211 raise MyThread.FatalException;
213212```
214213
215214Dieser Code sorgt dafür daß Exceptions, die während der Erzeugung des Threads auftreten, im Main Thread einen Ausnahmefehler auslösen.
216215
217216## Für Multithread-Anwendungen benötigte Units
218217
219218Unter Windows benötigen Sie keine spezielle Unit, damit es funktioniert. Unter Linux, MacOSX und FreeBSD benötigen Sie die Unit cthreads und diese *muss* die erste verwendete Unit im Projekt sein (die Programm-Unit, .lpr)!
220219
221220Daher sollte der Code Ihrer Lazarus-Anwendung etwa so aussehen:
222221
223222``` pascal
224223 program MyMultiThreadedProgram;
225224 {$mode objfpc}{$H+}
226225 uses
227226 {$ifdef unix}
228227 cthreads,
229228 {$endif}
230229 Interfaces, // dies bindet das LCL Widgetset ein
231230 Forms
232231 { fügen Sie Ihre Units hier hinzu },
233232```
234233
235234Wenn Sie dies vergessen, kommt die folgende Fehlermeldung:
236235
237236` This binary has no thread support compiled in.`\
238237` Recompile the application with a thread-driver in the program uses clause before other units using thread.`
239238
240239Lazarus Packages, die Multithreading beutzen, sollten **-dUseCThreads** zu den benutzerdefinierten Einstellungen für abhängige Pakete hinzufügen. Dazu öffnen Sie den Package-Editor für das entsprechende Paket, unter Einstellungen \> Bedienung \> Benutzerdefiniert fügen Sie *-dUseCThreads* hinzu. Dies wird diesen Compiler-Switch für alle Projekte und andere Packages, die dieses Package benutzen (die IDE eingeschlossen), definieren. Sowohl die IDE als auch alle von ihr neu erstellten Projekte, haben bereits folgenden Code in ihrer Projektdatei (.lpr):
241240
242241``` pascal
243242 uses
244243 {$IFDEF UNIX}{$IFDEF UseCThreads}
245244 cthreads,
246245 {$ENDIF}{$ENDIF}
247246```
248247
249248## SMP (Mehrprozessor) Unterstützung
250249
251250Sobald Sie Threads benutzen, werden diese vom Betriebsystem auf mehrere Prozessoren verteilt.
252251
253252## Debuging Multithread-Anwendungen mit Lazarus
254253
255254Das Debuggen von Multithread-Anwendungen wird von Lazarus zur Zeit noch nicht vollständig unterstützt.
256255
257256### Debugger Ausgabe
258257
259258In einer Anwendung mit einem Thread (dem Hauptthread), können Sie einfach auf die Konsole in ein Terminal oder ähnliches schreiben und die Reihenfolge der Zeilen wird chronologisch so angeordnet sein, wie Sie es ausgegeben haben. In Multithreaded Anwendungen wird dies etwas komplizierter. Wenn 2 Threads schreiben, sagen wir, eine Zeile wird von Thread A und eine von Thread B geschrieben, dann ist nicht gesagt, dass sie zeitlich in der richtigen Reihenfolge erscheinen, bzw. es kann vorkommen, dass ein Thread dem anderen in die Ausgabe hineinschreibt.
260259
261260Die Unit LCLProc enthält einige Funktionen, um jeden Thread seine eigene Logdatei schreiben zu lassen:
262261
263262``` pascal
264263 procedure DbgOutThreadLog(const Msg: string); overload;
265264 procedure DebuglnThreadLog(const Msg: string); overload;
266265 procedure DebuglnThreadLog(Args: array of const); overload;
267266 procedure DebuglnThreadLog; overload;
268267```
269268
270269Zum Beispiel: Anstelle von *writeln('irgendein Text ', 123);* verwenden Sie
271270
272271``` pascal
273272 DebuglnThreadLog(['irgendein Text ', 123]);
274273```
275274
276275Dies wird eine Zeile 'irgendein Text 123' an die **Log\<PID\>.txt** Datei anhängen, wobei \<PID\> die Prozess ID des aktuellen Threads ist.
277276
278277Es ist eine gute Idee, die Logdateien vor jedem Lauf zu entfernen:
279278
280279``` bash
281280 rm -f Log* && ./project1
282281```
283282
284283### Linux
285284
286285Das Debuggen von grafischen Multithreading-Anwendungen unter Linux funktioniert mittlerweile problemlos. In der Vergangenheit traten aber aufgrund von Kompatibilitätsproblemen dabei Probleme mit dem X-Server auf: Er stürzte ab.
287286
288287Sollten bei Ihnen also solche Probleme auftreten, können Sie diese durch Starten einer neuen X-Instanz umgehen. Wie Sie dies erreichen, ist im Folgenden dargestellt. Eine andere Lösung ist bisher nicht bekannt.
289288
290289#### X-Server in einem neuen Terminal starten
291290
292291Starten Sie eine neue X-Instanz. Dies können Sie über die Konsole mit folgendem Befehl erreichen:
293292
294293` X :1 &`
295294
296295Nachdem Sie diesen Befehl ausgeführt haben, öffnet sich die neue X-Instanz. Mit den Tastenkombinationen \[Strg\]+\[Alt\]+\[F7\] und \[Strg\]+\[Alt\]+\[F8\] können Sie nun zwischen der Arbeitsoberfläche, auf der Sie arbeiten, und der neuen Instanz hin und her wechseln. (Bei der Slackware-Distribution und wenigen anderen wird dies durch die Kombination \[Strg\]+\[Alt\]+\[F2\] erreicht)
297296
298297Als nächsten Schritt müssen Sie eine Desktop-Sitzung in der neuen X-Instanz starten. Starten Sie zum Beispiel eine neue gnome-Sitzung in der Konsole:
299298
300299` gnome-session --display=:1 &`
301300
302301#### X-Server in einem Fenster starten
303302
304303Mit Xnest oder Xephyr können Sie auch einen X-Server in einem Fenster eines anderen X-Servers betreiben. Der Vorteil ist, dass Sie beim Debuggen nicht mehr zwischen verschiedenen Terminals wechseln müssen.
305304
306305Mit dem Befehl
307306
308307` Xnest :1 -ac`
309308
310309starten Sie mit Xnest eine X-Sitzung auf :1 und deaktivieren die Zugriffskontrolle.
311310
312311Unter GNOME können Sie auch folgenden Befehl verwenden:
313312
314313` gdmflexiserver --xnest`
315314
316315Dies startet nicht nur eine X-Sitzung; es wird auch gleichzeitig eine neue GDM-Sitzung, in der Sie sich anmelden können, gestartet.
317316
318317#### Benötigte Einstellungen in Lazarus
319318
320319Schließlich müssen Sie eine letzte Einstellung in Lazarus selbst vornehmen: Gehen Sie in der IDE in das Menü "Start"-\>"Start-Parameter...". Aktivieren Sie dort das Häkchen "Display verwenden", und tragen Sie in das Feld darunter die Zuordnungsnummer ":1" ein.
321320
322321Jetzt wird die Anwendung auf dem zweiten X Server laufen und Sie sind in der Lage, sie vom ersten aus zu debuggen.
323322
324323Dies wurde mit Free Pascal 2.0 und Lazarus 0.9.10 unter Windows und Linux getestet.
325324
326325## Widgetsets
327326
328327Die win32, gtk und carbon Schnittstellen unterstützen Multithreading vollständig. Das bedeutet, TThread, critical sections und Synchronize funktioniert.
329328
330329## Critical sections
331330
332331Eine *critical section* (kritischer Abschnitt) stellt sicher, dass in einer multi-thread Anwendung nicht gleichzeitig von verschiedenen Threads aus auf einen bestimmten Bereich des Codes zugegriffen wird. Bevor eine critical section benutzt werden kann, muss sie erstellt und initialisiert werden; wenn sie nicht mehr gebraucht wird, muss sie freigegeben werden.
333332
334333Zur Verwendung von critical sections gehen Sie wie folgt vor:
335334
336335Fügen Sie über *uses* die Unit **SyncObjs** hinzu, die die notwendigen Routinen beinhaltet.
337336
338337Als nächsten Schritt deklarieren Sie die critical section (für alle Threads, die auf die section Zugriff haben sollen):
339338
340339``` pascal
341340 MyCriticalSection: TRTLCriticalSection;
342341```
343342
344343Erstellen Sie die Section mit folgender Prozedur:
345344
346345` InitCriticalSection(MyCriticalSection);`
347346
348347Starten Sie einige Threads, die exklusive Aufgaben durchführen:
349348
350349``` pascal
351350 EnterCriticalSection(MyCriticalSection);
352351 try
353352 // An dieser Stelle können Sie auf Variablen zugreifen, Dateien schreiben, auf das Netzwerk zugreifen, u.s.w.
354353 finally
355354 LeaveCriticalSection(MyCriticalSection);
356355 end;
357356```
358357
359358Nachdem alle Threads, die Zugriff auf die critical section hatten, beendet wurden, müssen Sie sie nun deinitialisieren:
360359
361360``` pascal
362361 DoneCriticalSection(MyCriticalSection);
363362```
364363
365364Alternativ können Sie die Klasse TCriticalSection benutzen. Das Erstellen eines Objektes der Klasse entspricht hier der Initialisierung, die Methode Enter entspricht obigem EnterCriticalSection, die Methode Leave entspricht der obigen LeaveCriticalSection und die Vernichtung (destruction) des Objektes erledigt das Freigeben.
366365
367366Beispiel: 5 Threads, die einen einzigen Zähler erhöhen: lazarus/examples/multithreading/criticalsectionexample1.lpi
368367
369368**ACHTUNG:** Sowohl die RTL als auch die LCL beinhalten beide obige 4 Funktionen. (Die Funktionen der LCL sind in den Units LCLIntf und LCLType definiert.) Beide erfüllen den gleichen Zweck. Sie können sowohl die Funktionen der RTL als auch die Funktionen der LCL zur selben Zeit in Ihrem Programm einsetzen. Sie sollten es nur vermeiden, die "critical section"-Funktionen der LCL auf die Funktionen der RTL anzuwenden und umgekehrt.
370369
371370### Gemeinsamer Variablenzugriff
372371
373372Mehrere Threads können sich eine Variable im Lesezugriff teilen, ohne dass Probleme zu erwarten sind. Nur wenn der eine oder andere Thread den Wert der Variablen ändern möchte, muss sicher gestellt werden, dass niemals mehrere Threads gleichzeitig auf die Variable zugreifen.
374373
375374Als Beispiel: 5 Threads inkrementieren einen Counter: lazarus/examples/multithreading/criticalsectionexample1.lpi
376375
377376## Wenn ein Thread das Ergebnis eines anderen benötigt...
378377
379378Wenn ein Thread A das Ergebnis eines Threads B benötigt, muss er warten, bis B fertig ist.
380379
381380**Wichtig:** Der Main-Thread sollte niemals auf einen anderen Thread warten müssen. Für diesen Fall benutzen Sie am Besten Synchronize (oben beschrieben).
382381
383382Beispiel: lazarus/examples/multithreading/waitforexample1.lpi
384383
385384``` pascal
386385{ TThreadA }
387386
388387procedure TThreadA.Execute;
389388begin
390389 Form1.ThreadB := TThreadB.Create(false);
391390 // create event
392391 WaitForB := RTLEventCreate;
393392 while not Application.Terminated do begin
394393 // wait infinitely (until B wakes A)
395394 RtlEventWaitFor(WaitForB);
396395 WriteLn('A: ThreadB.Counter=', Form1.ThreadB.Counter);
397396 end;
398397end;
399398
400399{ TThreadB }
401400
402401procedure TThreadB.Execute;
403402var
404403 i: Integer;
405404begin
406405 Counter := 0;
407406 while not Application.Terminated do begin
408407 // B: Working ...
409408 Sleep(1500);
410409 inc(Counter);
411410 // wake A
412411 RtlEventSetEvent(Form1.ThreadA.WaitForB);
413412 end;
414413end;
415414```
416415
417416## Fork
418417
419418When forking in a multithreaded application, be aware that any threads created and running BEFORE the fork (or fpFork) call, will NOT be running in the child process. As stated on the fork() man page, any threads that were running before the fork call, their state will be undefined.
420419
421420So be aware of any threads initializing before the call (including on the initialization section). They will NOT work.
422421
423422## Distributed computing
424423
425424The next higher steps after multi threading is running the treads on multiple machines.
426425
427426- You can use one of the TCP suites like synapse, lnet or indy for communications. This gives you maximum flexibility and is mostly used for loosely connected Client / Server applications.
428427- You can use message passing libraries like [[MPICH]], which are used for HPC (High Performance Computing) on clusters.
429428
430429---
431430*From the Free Pascal wiki, page "Multithreaded Application Tutorial/de", as it stood on 2023-06-23 after a change by Monta, 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