Sign in Join
Wiki copytemporary

Multithreaded Application Tutorial/pl: what changed

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

Title: Multithreaded Application Tutorial/pl

OldNewLine
1+<div style="font-size:1.88em;margin:0.75em 0;">
2+
3+Kurs tworzenia aplikacji wielowątkowych
4+
5+</div>
6+
7+## Wprowadzenie
8+
9+Na tej stronie spróbujemy wyjaśnić, w jaki sposób napisać i debugować aplikacje wielowątkowe przy pomocy Free Pascala i Lazarusa. Aplikacja wielowątkowa to program, który tworzy dwa lub więcej wątków wykonawczych działających w tym samym czasie. Jeśli nie miałeś dotąd do czynienia z wielowątkowością, przeczytaj akapit „Czy potrzebujesz wielowątkowości?” aby ustalić, czy jest ci to naprawdę potrzebne, bo być może zaoszczędzisz sobie bólu głowy.
10+
11+Pierwszy wątek nazywany jest głównym wątkiem. Główny wątek to ten, który jest tworzony przez System Operacyjny, to ten sam, w którym nasza aplikacja rozpoczyna działanie. Główny wątek **musi być** jedynym wątkiem, który aktualizuje komponenty do komunikacji z użytkownikiem: w przeciwnym wypadku, aplikacja może się zawiesić.
12+
13+Podstawowym założeniem jest to, aby aplikacja mogła przetwarzać pewne dane w tle, tj. w drugim wątku, podczas gdy użytkownik może kontynuować pracę przy użyciu głównego wątku.
14+
15+Innym zastosowaniem wątków jest po prostu możliwość lepszej reakcji programu. Jeśli tworzysz dużą aplikację lub gdy użytkownik naciśnie przycisk aplikacji rozpoczynając jakiś duży proces ... dopóki trwa przetwarzanie, ekran przestaje odpowiadać, co powoduje błędne lub mylące wrażenie, że aplikacja jest zawieszona. Jeśli duży proces przebiega w drugim wątku, aplikacja zachowuje się (prawie) tak, jakby była w stanie bezczynności. W tym przypadku dobrym pomysłem jest aby, przed rozpoczęciem wątku, wyłączyć odpowiednie przyciski na formularzu, w celu uniknięcia ponownego uruchomienia drugiego wątku przez użytkownika.
16+
17+Jeszcze innym zastosowaniem wielowątkowości może być serwer, który jest w stanie dać odpowiedź wielu klientom, w tym samym czasie.
18+
19+## Czy potrzebujesz wielowątkowości?
20+
21+Jeśli dopiero poznajesz wielowątkowość i chciałbyś tylko stworzyć aplikację z szybszym czasem reakcji w chwili gdy wykonuje ona umiarkowanie długotrwałe zadanie, wówczas wielowątkowość może być nadmiarowa w stosunku do wymagań. Aplikacje wielowątkowe są zawsze trudniejsze do debugowania i często są znacznie bardziej skomplikowane, jednak w wielu przypadkach wcale nie potrzebujesz używać wielowątkowości. Jeden wątek jest wystarczający. Możesz podzielić czasochłonne zadanie na kilka mniejszych części, oraz użyć procedurę Application.ProcessMessages. Procedura ta pozwala bibliotece LCL obsłużyć wszystkie oczekujące komunikaty i powrócić do miejsca jej wywołania. Główną ideą jest to, aby wywoływać Application.ProcessMessages w regularnych odstępach czasu w trakcie wykonywania długotrwałego zadania, np. po to aby sprawdzić, czy użytkownik kliknął na jakąś kontrolkę lub czy wskaźnik postępu musi zostać przemalowany albo zdarzyło się jeszcze coś innego.
22+
23+Przykład: Czytanie dużego pliku i jego przetwarzanie. Zobacz: examples/multithreading/singlethreadingexample1.lpi.
24+
25+Wielowątkowość jest potrzebna tylko w przypadku
26+
27+- używania uchwytów blokujących, takich jak w komunikacji sieci
28+- korzystania z wielu procesorów jednocześnie (SMP)
29+- algorytmów i bibliotek, które muszą być wywoływane przez API i jako takie nie mogą być podzielone na mniejsze części.
30+
31+Jeśli chcesz użyć wielowątkowości, aby zwiększyć prędkość przy użyciu wielu procesorów jednocześnie, sprawdź, czy twój obecny program w tej chwili wykorzystuje 100% zasobów 1 rdzenia CPU (na przykład, program może aktywnie korzystać z operacji wejścia-wyjścia, jak zapis do pliku i to zajmuje dużo czasu, ale nie obciąża procesora, i w tym przypadku program nie będzie działał szybciej z wieloma wątkami). Należy również sprawdzić, czy poziom optymalizacji jest ustawiony na maksymalny (3). Przełączając poziom optymalizacji z 1 na 3, program może stać się około 5 razy szybszy.
32+
33+## Moduły potrzebne do tworzenia aplikacji wielowątkowej
34+
35+Nie potrzebujesz żadnych specjalnych modułów do tego, aby pracować z systemem Windows. Jednak w przypadku Linuksa, Mac OS X i FreeBSD, należy użyć modułu cthreads i *musi* być on użyty jako pierwszy moduł projektu (program źródłowy, zwykle znajdujący się w pliku .lpr)!
36+
37+Dlatego twój kod aplikacji Lazarusa powinien wyglądać tak:
38+
39+``` pascal
40+program MyMultiThreadedProgram;
41+{$mode objfpc}{$H+}
42+uses
43+{$ifdef unix}
44+ cthreads,
45+ cmem, // menedżer pamięci c jest w niektórych systemach znacznie szybszy dla aplikacji wielowątkowych
46+{$endif}
47+ Interfaces, // to zawiera widżety LCL
48+ Forms
49+ { tu możesz dodać następne moduły },
50+```
51+
52+Jeśli o tym zapomnisz i użyjesz TThread, otrzymasz następujący błąd podczas uruchamiania:
53+
54+` This binary has no thread support compiled in.`\
55+` Recompile the application with a thread-driver in the program uses clause before other units using thread.`
56+
57+> **Note:** Jeśli pojawi się błąd linkera o tym, że nie znaleziono „mcount” to znaczy, że używasz jakiegoś modułu, który zawiera kod wielowątkowy i musisz dodać moduł cthreads lub użyć [[smart linking|sprytnego łączenia]].
58+
59+> **Note:** Jeśli w procedurze SYSTEM_NOTHREADERROR otrzymasz błąd: „Project raised exception class 'RunError(232)'” to znaczy, że twój kod potrzebuje wątków i musisz dodać moduł cthreads.
60+
61+## Przykład w czystym FPC
62+
63+Poniższy kod przedstawia bardzo prosty przykład. Testowany z FPC 3.0.4 na Win7.
64+
65+``` pascal
66+Program ThreadTest;
67+{ przetestuj możliwość aplikacji wielowątkowej }
68+{
69+ OUTPUT
70+wątek 1 uruchomiony
71+wątek 1 zmienna thri 0 Długość(s)= 1
72+wątek 1 zmienna thri 1 Długość(s)= 2
73+wątek 1 zmienna thri 2 Długość(s)= 3
74+wątek 1 zmienna thri 3 Długość(s)= 4
75+wątek 1 zmienna thri 4 Długość(s)= 5
76+wątek 1 zmienna thri 5 Długość(s)= 6
77+wątek 1 zmienna thri 6 Długość(s)= 7
78+wątek 1 zmienna thri 7 Długość(s)= 8
79+wątek 1 zmienna thri 8 Długość(s)= 9
80+wątek 1 zmienna thri 9 Długość(s)= 10
81+wątek 1 zmienna thri 10 Długość(s)= 11
82+wątek 1 zmienna thri 11 Długość(s)= 12
83+wątek 1 zmienna thri 12 Długość(s)= 13
84+wątek 1 zmienna thri 13 Długość(s)= 14
85+wątek 1 zmienna thri 14 Długość(s)= 15
86+wątek 2 uruchomiony
87+wątek 3 uruchomiony
88+wątek 1 zmienna thri 15 Długość(s)= 16
89+wątek 2 zmienna thri 0 Długość(s)= 1
90+wątek 3 zmienna thri 0 Długość(s)= 1
91+wątek 1 zmienna thri 16 Długość(s)= 17
92+...
93+...
94+wątek 5 zmienna thri 997 Długość(s)= 998
95+wątek 5 zmienna thri 998 Długość(s)= 999
96+wątek 5 zmienna thri 999 Długość(s)= 1000
97+wątek 5 zakończony
98+wątek 10 zmienna thri 828 Długość(s)= 829
99+wątek 9 zmienna thri 675 Długość(s)= 676
100+wątek 4 zmienna thri 656 Długość(s)= 657
101+wątek 10 zmienna thri 829 Długość(s)= 830
102+wątek 9 zmienna thri 676 Długość(s)= 677
103+wątek 9 zmienna thri 677 Długość(s)= 678
104+wątek 10 zmienna thri 830 Długość(s)= 831
105+wątek 10 zmienna thri 831 Długość(s)= 832
106+wątek 10 zmienna thri 832 Długość(s)= 833
107+wątek 10 zmienna thri 833 Długość(s)= 834
108+wątek 10 zmienna thri 834 Długość(s)= 835
109+wątek 10 zmienna thri 835 Długość(s)= 836
110+wątek 10 zmienna thri 836 Długość(s)= 837
111+wątek 10 zmienna thri 837 Długość(s)= 838
112+wątek 10 zmienna thri 838 Długość(s)= 839
113+wątek 10 zmienna thri 839 Długość(s)= 840
114+wątek 9 zmienna thri 678 Długość(s)= 679
115+...
116+...
117+wątek 4 zmienna thri 994 Długość(s)= 995
118+wątek 4 zmienna thri 995 Długość(s)= 996
119+wątek 4 zmienna thri 996 Długość(s)= 997
120+wątek 4 zmienna thri 997 Długość(s)= 998
121+wątek 4 zmienna thri 998 Długość(s)= 999
122+wątek 4 zmienna thri 999 Długość(s)= 1000
123+wątek 4 zakończony
124+10
125+
126+}
127+
128+uses
129+ {$ifdef unix}cthreads, {$endif} sysutils;
130+
131+const
132+ threadcount = 10;
133+ stringlen = 1000;
134+
135+var
136+ finished : longint;
137+
138+threadvar
139+ thri : ptrint;
140+
141+function f(p : pointer) : ptrint;
142+var
143+ s : ansistring;
144+begin
145+ Writeln('wątek ',longint(p),' uruchomiony');
146+ thri:=0;
147+ while (thri<stringlen) do begin
148+ s:=s+'1'; { stwórz opóźnienie }
149+ writeln('wątek ',longint(p),' zmienna thri ',thri,' Długość(s)= ',length(s));
150+ inc(thri);
151+ end;
152+ Writeln('wątek ',longint(p),' zakończony');
153+ InterLockedIncrement(finished);
154+ f:=0;
155+end;
156+
157+var
158+ i : longint;
159+
160+Begin
161+ finished:=0;
162+ for i:=1 to threadcount do
163+ BeginThread(@f,pointer(i));
164+ while finished<threadcount do ;
165+ Writeln(finished);
166+End.
167+```
168+
169+## Klasa TThread
170+
171+Poniższy przykład można znaleźć w katalogu examples/multithreading/.
172+
173+Aby utworzyć aplikację wielowątkową, najłatwiej jest użyć klasy TThread. Ta klasa pozwala w prosty sposób stworzyć dodatkowy wątek (obok głównego wątku). Zwykle wymagane jest przesłonięcie tylko dwóch metod: konstruktora Create i metody Execute.
174+
175+W konstruktorze przygotujesz wątek do uruchomienia. Ustawisz początkowe wartości dla zmiennych lub właściwości, których potrzebujesz. Oryginalny konstruktor TThread wymaga parametru o nazwie Suspended. Jak można się spodziewać, ustawienie Suspended = True zapobiegnie automatycznemu uruchomieniu wątku po utworzeniu. Jeśli Suspended = False, wątek zacznie działać zaraz po utworzeniu. Jeśli wątek zostanie utworzony jako zawieszony, zostanie uruchomiony dopiero po wywołaniu metody Start.
176+
177+> **Note:** Metoda Resume jest [przestarzała od FPC 2.4.4](http://wiki.freepascal.org/User_Changes_2.4.4#TThread.Suspend_and_TThread.Resume_have_been_deprecated). Została zastąpiona przez metodę Start.
178+
179+Począwszy od wersji FPC 2.0.1 i nowszych, TThread.Create ma również niejawny parametr dla rozmiaru stosu. W razie potrzeby możesz teraz zmienić domyślny rozmiar stosu każdego utworzonego wątku. Dobrym przykładem są głębokie rekurencje wywołań procedur w wątku. Jeśli nie określisz parametru rozmiaru stosu, zostanie użyty domyślny rozmiar stosu systemu operacyjnego.
180+
181+W zastąpionej metodzie Execute napisz kod, który będzie uruchamiany w wątku.
182+
183+Klasa TThread ma jedną ważną właściwość: Terminated : boolean;
184+
185+Jeśli wątek ma pętlę (i jest to typowe), pętla powinna zostać zakończona, gdy Terminated ma wartość true (domyślnie jest to false). W każdym przebiegu należy sprawdzić wartość Terminated, a jeśli jest prawdziwa, pętla powinna zostać zakończona tak szybko, jak to konieczne, po każdym koniecznym czyszczeniu. ~~Należy pamiętać, że metoda Terminate domyślnie nic nie robi: metoda .Execute musi jawnie zaimplementować obsługę, aby zakończyć swoje zadanie.~~ Metoda Terminate domyślnie ustawia jedynie właściwość Terminated na wartość **True**.
186+
187+Jak wyjaśniliśmy wcześniej, wątek nie powinien wchodzić w interakcje z widocznymi komponentami. Aktualizacje widocznych komponentów muszą być dokonywane w kontekście głównego wątku.
188+
189+Aby to zrobić, istnieje metoda TThread o nazwie Synchronize. Synchronize wymaga metody w wątku (która nie przyjmuje parametrów) jako argumentu. Po wywołaniu tej metody za pomocą Synchronize(@MyMethod) wykonanie wątku zostanie wstrzymane, kod MyMethod zostanie wywołany z głównego wątku, a następnie zostanie wznowione wykonywanie wątku.
190+
191+Dokładne działanie metody Synchronize zależy od platformy, ale zasadniczo wykonuje ona:
192+
193+- wysyła wiadomość do głównej kolejki wiadomości i zasypia
194+- ostatecznie główny wątek przetwarza wiadomość i wywołuje MyMethod. W ten sposób MyMethod jest wywoływana bez kontekstu, co oznacza, że ​​nie wykonuje się podczas zdarzenia wciśnięcia myszy lub podczas malowania, ale po.
195+- po wykonaniu przez główny wątek MyMethod, budzi ona uśpiony wątek i przetwarza następną wiadomość
196+- następnie wątek jest kontynuowany.
197+
198+Jest jeszcze jedna ważna właściwość TThread: FreeOnTerminate. Jeśli ta właściwość ma wartość true, obiekt wątku jest automatycznie zwalniany po zatrzymaniu wykonywania wątku (metoda .Execute). W przeciwnym razie aplikacja będzie musiała zwolnić ją ręcznie.
199+
200+Przykład:
201+
202+``` pascal
203+ Type
204+ TMyThread = class(TThread)
205+ private
206+ fStatusText : string;
207+ procedure ShowStatus;
208+ protected
209+ procedure Execute; override;
210+ public
211+ Constructor Create(CreateSuspended : boolean);
212+ end;
213+ constructor TMyThread.Create(CreateSuspended : boolean);
214+ begin
215+ inherited Create(CreateSuspended);
216+ FreeOnTerminate := True;
217+ end;
218+
219+ procedure TMyThread.ShowStatus;
220+ // ta metoda jest wykonywana przez główny wątek i dlatego może uzyskać dostęp do wszystkich elementów GUI.
221+ begin
222+ Form1.Caption := fStatusText;
223+ end;
224+
225+ procedure TMyThread.Execute;
226+ var
227+ newStatus : string;
228+ begin
229+ fStatusText := 'TMyThread Starting...';
230+ Synchronize(@Showstatus);
231+ fStatusText := 'TMyThread Running...';
232+ while (not Terminated) and ([inne wymagane warunki]) do
233+ begin
234+ ...
235+ [tutaj jest kod głównej pętli wątku]
236+ ...
237+ if NewStatus <> fStatusText then
238+ begin
239+ fStatusText := newStatus;
240+ Synchronize(@Showstatus);
241+ end;
242+ end;
243+ end;
244+```
245+
246+W aplikacji:
247+
248+``` pascal
249+ var
250+ MyThread : TMyThread;
251+ begin
252+ MyThread := TMyThread.Create(True); // W ten sposób nie uruchamia się automatycznie
253+ ...
254+ [Tutaj kod inicjuje wszystko, co jest wymagane, zanim wątki zaczną się wykonywać]
255+ ...
256+ MyThread.Start;
257+ end;
258+```
259+
260+Jeśli chcesz, aby Twoja aplikacja była bardziej elastyczna, możesz utworzyć zdarzenie dla wątku; w ten sposób Twoja zsynchronizowana metoda nie będzie ściśle powiązana z określoną formą lub klasą: możesz dołączyć detektory do zdarzenia wątku. Oto przykład:
261+
262+``` pascal
263+ Type
264+ TShowStatusEvent = procedure(Status: String) of Object;
265+
266+ TMyThread = class(TThread)
267+ private
268+ fStatusText : string;
269+ FOnShowStatus: TShowStatusEvent;
270+ procedure ShowStatus;
271+ protected
272+ procedure Execute; override;
273+ public
274+ Constructor Create(CreateSuspended : boolean);
275+ property OnShowStatus: TShowStatusEvent read FOnShowStatus write FOnShowStatus;
276+ end;
277+
278+ constructor TMyThread.Create(CreateSuspended : boolean);
279+ begin
280+ inherited Create(CreateSuspended);
281+ FreeOnTerminate := True;
282+ end;
283+
284+ procedure TMyThread.ShowStatus;
285+ // ta metoda jest wykonywana przez główny wątek i dlatego może uzyskać dostęp do wszystkich elementów GUI.
286+ begin
287+ if Assigned(FOnShowStatus) then
288+ begin
289+ FOnShowStatus(fStatusText);
290+ end;
291+ end;
292+
293+ procedure TMyThread.Execute;
294+ var
295+ newStatus : string;
296+ begin
297+ fStatusText := 'TMyThread Starting...';
298+ Synchronize(@Showstatus);
299+ fStatusText := 'TMyThread Running...';
300+ while (not Terminated) and ([inne wymagane warunki]) do
301+ begin
302+ ...
303+ [tutaj jest kod głównej pętli wątku]
304+ ...
305+ if NewStatus <> fStatusText then
306+ begin
307+ fStatusText := newStatus;
308+ Synchronize(@Showstatus);
309+ end;
310+ end;
311+ end;
312+```
313+
314+W aplikacji:
315+
316+``` pascal
317+ Type
318+ TForm1 = class(TForm)
319+ Button1: TButton;
320+ Label1: TLabel;
321+ procedure Button1Click(Sender: TObject);
322+ procedure FormCreate(Sender: TObject);
323+ procedure FormDestroy(Sender: TObject);
324+ private
325+ { private declarations }
326+ MyThread: TMyThread;
327+ procedure ShowStatus(Status: string);
328+ public
329+ { public declarations }
330+ end;
331+
332+ procedure TForm1.FormCreate(Sender: TObject);
333+ begin
334+ inherited;
335+ MyThread := TMyThread.Create(true);
336+ MyThread.OnShowStatus := @ShowStatus;
337+ end;
338+
339+ procedure TForm1.FormDestroy(Sender: TObject);
340+ begin
341+ MyThread.Terminate;
342+
343+ // FreeOnTerminate ma wartość true więc nie powinniśmy pisać:
344+ // MyThread.Free;
345+ inherited;
346+ end;
347+
348+ procedure TForm1.Button1Click(Sender: TObject);
349+ begin
350+ MyThread.Start;
351+ end;
352+
353+ procedure TForm1.ShowStatus(Status: string);
354+ begin
355+ Label1.Caption := Status;
356+ end;
357+```
358+
359+## Ważne rzeczy, na które trzeba zwrócić uwagę
360+
361+### Kontrola stosu w systemie Windows
362+
363+Może wystąpić potencjalny problem w systemie Windows, jeśli używasz przełącznika -Ct (sprawdzanie stosu). Z powodów, które nie są do końca jasne, sprawdzanie stosu będzie „wyzwalane” z każdym wywołaniem TThread.Create, jeśli używasz domyślnego rozmiaru stosu. W tej chwili jedynym rozwiązaniem jest po prostu nieużywanie przełącznika -Ct. Zauważ, że NIE powoduje to wyjątku w głównym wątku, ale w nowo utworzonym. To „wygląda” tak, jakby wątek nigdy nie został uruchomiony.
364+
365+Poprawny kod do sprawdzenia tego i innych wyjątków, które mogą wystąpić podczas tworzenia wątków, to:
366+
367+``` pascal
368+MyThread := TThread.Create(False);
369+
370+if Assigned(MyThread.FatalException) then
371+ raise MyThread.FatalException;
372+```
373+
374+Ten kod zapewni, że każdy wyjątek, który wystąpił podczas tworzenia wątku, zostanie zgłoszony w głównym wątku.
375+
376+### Wielowątkowość w pakietach
377+
378+Pakiety korzystające z wielowątkowości powinny dodawać flagę **-dUseCThreads** do dodatkowo używanych opcji. Otwórz edytor pakietów, a następnie Opcje \> Użycie \> Dodatkowe i dodaj *-dUseCThreads*. Spowoduje to zdefiniowanie tej flagi dla wszystkich projektów i pakietów korzystających z tego pakietu, w tym IDE. IDE i wszystkie nowe aplikacje utworzone przez IDE mają już następujący kod w swoim pliku .lpr:
379+
380+``` pascal
381+uses
382+ {$IFDEF UNIX}{$IFDEF UseCThreads}
383+ cthreads,
384+ cmem, // Menedżer pamięci c jest w niektórych systemach znacznie szybszy w przypadku wielowątkowości
385+ {$ENDIF}{$ENDIF}
386+```
387+
388+### Moduł Heaptrc
389+
390+Nie można używać przełącznika -gh z modułem *cmem*. Przełącznik -gh używa modułu heaptrc, który rozszerza menedżer stosu. Dlatego moduł **heaptrc** musi być używany **po** module **cmem**.
391+
392+``` pascal
393+uses
394+ {$IFDEF UNIX}{$IFDEF UseCThreads}
395+ cthreads,
396+ cmem, // Menedżer pamięci c jest w niektórych systemach znacznie szybszy w przypadku wielowątkowości
397+ {$ENDIF}{$ENDIF}
398+ heaptrc,
399+```
400+
401+### Initialization and Finalization
402+
403+Aby zainicjować sam obiekt wątku, możesz uruchomić go w trybie zawieszenia i ustawić jego właściwości i/lub utworzyć nowy konstruktor i wywołać odziedziczony konstruktor.
404+
405+**Uwaga**: Używanie AfterConstruction, gdy CreateSuspended=false jest niebezpieczne, ponieważ wątek już zaczął działać.
406+
407+Z drugiej strony, destruktor może służyć do finalizowania zasobów obiektu.
408+
409+``` pascal
410+type
411+ TMyThread = class(TThread)
412+ private
413+ fRTLEvent: PRTLEvent;
414+ public
415+ procedure Create(SomeData: TSomeObject); override;
416+ destructor Destroy; override;
417+ end;
418+
419+procedure TMyThread.Create(SomeData: TSomeObject; CreateSuspended: boolean);
420+begin
421+ // przykład: skonfiguruj zdarzenia, sekcje krytyczne i inne zasoby, takie jak pliki lub połączenia z bazami danych
422+ RTLEventCreate(fRTLEvent);
423+ inherited Create(CreateSuspended);
424+end;
425+
426+destructor TMyThread.Destroy;
427+begin
428+ RTLeventDestroy(fRTLEvent);
429+ inherited Destroy;
430+end;
431+```
432+
433+### Program bez LCL
434+
435+TThread.Synchronize wymaga, aby główny wątek regularnie wywoływał CheckSynchronize. LCL robi to w swojej pętli. Jeśli nie używasz pętli zdarzeń LCL, musisz ją wywołać samodzielnie.
436+
437+## Wsparcie dla SMP
438+
439+Dobrą wiadomością jest to, że jeśli Twoja aplikacja działa poprawnie wielowątkowo, to w ten sposób, jest już włączona obsługa SMP, czyli przetwarzanie wątków przez wiele procesorów!
440+
441+## Debugowanie aplikacji wielowątkowych w Lazarusie
442+
443+Debugowanie na Lazarusie wymaga [[GDB/pl|GDB]] i szybko staje się coraz bardziej funkcjonalne i stabilne. Jednak nadal istnieje kilka dystrybucji Linuksa z pewnymi problemami.
444+
445+### Wyjście debuggera
446+
447+W jednowątkowej aplikacji możesz po prostu pisać do konsoli/terminala/gdziekolwiek, a kolejność wierszy jest taka sama, jak zostały one napisane. W aplikacjach wielowątkowych sprawy są bardziej skomplikowane. Jeśli dwa wątki piszą, w taki sposób, że jakiś wiersz jest zapisywany przez wątek A przed wierszem przez wątek B, to wiersze niekoniecznie są zapisywane w tej kolejności. Może się nawet zdarzyć, że wątek zapisuje swoje wyjście, w tym samy czasie gdy drugi wątek zapisuje linię. W tej sytuacji pod Linuksem (być może) otrzymasz poprawne wyjście DebugLn(), a pod win32 możesz otrzymać wyjątek (prawdopodobnie DiskFull) z powodu użycia DebugLn() poza głównym wątkiem. Tak więc, aby uniknąć bólów głowy, użyj DebugLnThreadLog() wspomnianego poniżej.
448+
449+Moduł LCLProc zawiera kilka funkcji, które pozwalają każdemu wątkowi zapisywać do własnego pliku dziennika:
450+
451+``` pascal
452+ procedure DbgOutThreadLog(const Msg: string); overload;
453+ procedure DebuglnThreadLog(const Msg: string); overload;
454+ procedure DebuglnThreadLog(Args: array of const); overload;
455+ procedure DebuglnThreadLog; overload;
456+```
457+
458+Na przykład: Zamiast writeln('Jakiś tekst', 123); użyj
459+
460+` DebuglnThreadLog(['Jakiś tekst ',123]);`
461+
462+Spowoduje to dodanie linii 'Jakiś tekst 123' do **Log\<PID\>.txt**, gdzie \<PID\> jest identyfikatorem procesu bieżącego wątku.
463+
464+Dobrym pomysłem jest usunięcie plików dziennika przed każdym uruchomieniem:
465+
466+` rm -f Log* && ./project1`
467+
468+### Linux
469+
470+Jeśli spróbujesz debugować aplikację wielowątkową w systemie Linux, będziesz miał jeden duży problem: Menedżer pulpitu na serwerze X może się zawiesić. Dzieje się tak na przykład, gdy aplikacja przechwyciła mysz/klawiaturę i została zatrzymana przez gdb, a serwer X czeka na twoją aplikację. Kiedy tak się stanie, możesz po prostu zalogować się z innego komputera i zabić gdb lub wyjść z tej sesji, naciskając CTRL+ALT+F3 i zabić gdb. Alternatywnie możesz ponownie uruchomić menedżera okien: wpisz sudo /etc/init.d/gdm restart. Spowoduje to ponowne uruchomienie menedżera pulpitu i powrót do pulpitu.
471+
472+Ponieważ zależy to od tego, gdzie gdb zatrzymuje twój program, w niektórych przypadkach mogą pomóc pewne sztuczki: dla Ubuntu x64 ustaw opcje projektu do debugowania wymaganego dodatkowego pliku informacyjnego...
473+
474+` Projekt -> Opcje projektu -> Opcje kompilatora -> Odpluskwianie, i zaznacz: Use external gdb debug symbols file (-Xg).`
475+
476+Inną opcją jest otwarcie innego pulpitu X, uruchomienie IDE/gdb na jednym i aplikacji na drugim, tak aby zawieszał się tylko pulpit testowy. Utwórz nową instancję X za pomocą:
477+
478+` X :1 &`
479+
480+Gdy pulpit się otworzy, możesz przełączyć się na inny pulpit (ten, z którym pracujesz, naciskając CTRL + ALT + F7), będziesz mógł wrócić do nowego pulpitu graficznego za pomocą CTRL + ALT + F8 (jeśli ta kombinacja nie działa, spróbuj z CTRL + ALT + F2 ... ta kombinacja klawiszy działała w [Slackware](http://www.slackware.com)).
481+
482+Następnie możesz, jeśli chcesz, utworzyć sesję pulpitu na X, za pomocą polecenia:
483+
484+` gnome-session --display=:1 &`
485+
486+Następnie w Lazarusie w oknie dialogowym parametrów uruchamiania projektu (Uruchom -\> Uruchom z parametrami -\> Ekran) zaznacz opcję „Użyj ekranu” i wpisz :1.
487+
488+Teraz aplikacja będzie działać na drugim serwerze X i będziesz mógł ją debugować na pierwszym.
489+
490+Zostało to przetestowane z Free Pascal 2.0 i Lazarus 0.9.10 w systemie Linux.
491+
492+------------------------------------------------------------------------
493+
494+Zamiast tworzyć nową sesję X, można użyć Xnest. Xnest to sesja X w oknie. Korzystanie z niego sprawia, że X serwer nie blokuje się podczas debugowania wątków i jest znacznie łatwiej debugować bez konieczności zmiany terminali.
495+
496+Linia poleceń do uruchomienia Xnest to
497+
498+` Xnest :1 -ac`
499+
500+tworzy sesję X na :1 i wyłącza kontrolę dostępu.
501+
502+## Interfejsy widżetów Lazarusa
503+
504+Win32, gtk i interfejsy carbon obsługują wielowątkowość. Oznacza to, że działają tu TThread, krytyczne sekcje i Synchronize. Ale nie są bezpieczne wątkowo. To znaczy, że tylko jeden wątek na raz może uzyskać dostęp do LCL. Ponieważ główny wątek nigdy nie powinien czekać na kolejny wątek, to sprawia, że tylko wątek główny może uzyskać dostęp do LCL, czyli do wszystkiego, co ma związek z uchwytami TControl, Application i widgetów LCL. W LCL jest kilka funkcji bezpiecznych dla wątków. Na przykład większość funkcji w module FileUtil jest bezpieczna wątkowo.
505+
506+### Użycie SendMessage/PostMessage do komunikacji pomiędzy wątkami
507+
508+Tylko jeden wątek w aplikacji powinien wywoływać interfejsy API LCL, zwykle wątek główny. Inne wątki mogą korzystać z LCL za pomocą wielu metod pośrednich, jedną z dobrych opcji jest użycie SendMessage lub PostMessage. LCLIntf.SendMessage i LCLIntf.PostMessage wyślą wiadomość skierowaną do okna w puli wiadomości aplikacji.
509+
510+Zobacz także dokumentację tych procedur:
511+
512+- [[doc:lcl/lclintf/sendmessage.html|SendMessage]]
513+- [[doc:lcl/lclintf/postmessage.html|PostMessage]]
514+
515+Różnica między SendMessage i PostMessage polega na sposobie, w jaki zwracają kontrolę do wątku wywołującego. Podobnie jak dla Synchronize, w SendMessage bloki i kontrola nie są zwracane, dopóki okno, do którego wiadomość została wysłana, nie skończy jej przetwarzać; jednak w pewnych okolicznościach SendMessage może próbować zoptymalizować przetwarzanie, pozostając w kontekście wątku, który go wywołał. Dzięki PostMessage kontrola jest zwracana natychmiast do określonej przez system maksymalnej liczby umieszczonych w kolejce wiadomości i tak długo, jak na stercie pozostaje miejsce na dołączane dane.
516+
517+W obu przypadkach procedura obsługująca komunikat (patrz niżej) powinna unikać wywoływania application.ProcessMessages, ponieważ może to spowodować wysłanie drugiego komunikatu, który zostanie obsłużony ponownie. Jeśli jest to nieuniknione, prawdopodobnie lepiej byłoby użyć innego mechanizmu do przesyłania serializowanych zdarzeń między wątkami.
518+
519+Oto przykład, w jaki wątek dodatkowy może wysłać tekst, który ma być wyświetlany w kontrolce LCL do wątku głównego:
520+
521+``` pascal
522+const
523+ WM_GOT_ERROR = LM_USER + 2004;
524+ WM_VERBOSE = LM_USER + 2005;
525+
526+procedure VerboseLog(Msg: string);
527+var
528+ PError: PChar;
529+begin
530+ if MessageHandler = 0 then Exit;
531+ PError := StrAlloc(Length(Msg)+1);
532+ StrCopy(PError, PChar(Msg));
533+ PostMessage(formConsole.Handle, WM_VERBOSE, Integer(PError), 0);
534+end;
535+```
536+
537+I przykład, jak obsłużyć tę wiadomość z okna:
538+
539+``` pascal
540+const
541+ WM_GOT_ERROR = LM_USER + 2004;
542+ WM_VERBOSE = LM_USER + 2005;
543+
544+type
545+ { TformConsole }
546+
547+ TformConsole = class(TForm)
548+ DebugList: TListView;
549+ // ...
550+ private
551+ procedure HandleDebug(var Msg: TLMessage); message WM_VERBOSE;
552+ end;
553+
554+var
555+ formConsole: TformConsole;
556+
557+implementation
558+
559+....
560+
561+{ TformConsole }
562+
563+procedure TformConsole.HandleDebug(var Msg: TLMessage);
564+var
565+ Item: TListItem;
566+ MsgStr: PChar;
567+ MsgPasStr: string;
568+begin
569+ MsgStr := PChar(Msg.wparam);
570+ MsgPasStr := StrPas(MsgStr);
571+ Item := DebugList.Items.Add;
572+ Item.Caption := TimeToStr(SysUtils.Now);
573+ Item.SubItems.Add(MsgPasStr);
574+ Item.MakeVisible(False);
575+
576+// Po którym następuje coś takiego
577+
578+ TrayControl.SetError(MsgPasStr);
579+ StrDispose(MsgStr)
580+end;
581+
582+end.
583+```
584+
585+## Sekcje krytyczne
586+
587+*Sekcja krytyczna* to obiekt służący do upewnienia się, że jakaś część kodu jest wykonywana tylko przez jeden wątek na raz. Sekcja krytyczna musi zostać utworzona/zainicjowana, zanim będzie można jej użyć i zwolniona, gdy nie będzie już potrzebna.
588+
589+Sekcje krytyczne zwykle są używane w ten sposób:
590+
591+Deklaracja sekcji (globalnie dla wszystkich wątków, które powinny mieć dostęp do tej sekcji):
592+
593+` MyCriticalSection: TRTLCriticalSection;`
594+
595+Utworzenie sekcji:
596+
597+` InitializeCriticalSection(MyCriticalSection);`
598+
599+Uruchomienie kilka wątków. Robienie czegoś na wyłączność:
600+
601+``` pascal
602+EnterCriticalSection(MyCriticalSection);
603+try
604+ // uzyskaj dostęp do zmiennych, zapisz pliki, wyślij pakiety sieciowe itp.
605+finally
606+ LeaveCriticalSection(MyCriticalSection);
607+end;
608+```
609+
610+Po zakończeniu wszystkich wątków zwolnij sekcję krytyczną:
611+
612+` DeleteCriticalSection(MyCriticalSection);`
613+
614+Alternatywnie możesz użyć obiektu TCriticalSection. Inicjalizację wykonujemy przez jego utworzenie, metoda Enter wykonuje EnterCriticalSection, metoda Leave wykonuje LeaveCriticalSection, a zniszczenie obiektu powoduje jego usunięcie.
615+
616+Należy zauważyć, że sekcja krytyczna nie chroni przed wprowadzeniem tego samego wątku do tego samego bloku kodu, tylko przed różnymi wątkami. Z tego powodu nie może służyć do ochrony m.in. przed ponownym wprowadzeniem do programu obsługi wiadomości (patrz sekcja wyżej).
617+
618+Na przykład: 5 wątków zwiększających licznik. Zobacz lazarus/examples/multithreading/criticalsectionexample1.lpi
619+
620+> **Warning:** Istnieją dwa zestawy powyższych czterech funkcji. Znajdują się w RTL i LCL. Te w LCL są zdefiniowane w modułach LCLIntf i LCLType. Oba działają prawie tak samo. Możesz używać obu jednocześnie w swojej aplikacji, ale nie powinieneś używać funkcji RTL w sekcji krytycznej LCL i na odwrót.
621+
622+### Współdzielenie zmiennych
623+
624+Jeśli niektóre wątki współdzielą zmienną, która jest tylko do odczytu, nie ma się czym martwić. Po prostu odczytaj jej wartość. Ale jeśli jeden lub kilka wątków zmienia wartość zmiennej, to musisz upewnić się, że tylko jeden wątek ma dostęp do tej zmiennej w danej chwili.
625+
626+Na przykład: 5 wątków zwiększających licznik. Zobacz lazarus/examples/multithreading/criticalsectionexample1.lpi
627+
628+## Oczekiwanie na inny wątek
629+
630+Jeśli wątek A potrzebuje wyniku innego wątku B, musi on poczekać, aż B zakończy pracę.
631+
632+**Ważne:** Główny wątek nigdy nie powinien czekać na kolejny wątek. Zamiast tego użyj opcji Synchronizuj (patrz wyżej).
633+
634+Zobacz przykład: lazarus/examples/multithreading/waitforexample1.lpi
635+
636+``` pascal
637+{ TThreadA }
638+
639+procedure TThreadA.Execute;
640+begin
641+ Form1.ThreadB:=TThreadB.Create(false);
642+ // Utwórz zdarzenie
643+ WaitForB:=RTLEventCreate;
644+ while not Application.Terminated do begin
645+ // czekaj w nieskończoność (aż B obudzi A)
646+ RtlEventWaitFor(WaitForB);
647+ writeln('A: ThreadB.Counter='+IntToStr(Form1.ThreadB.Counter));
648+ end;
649+end;
650+
651+{ TThreadB }
652+
653+procedure TThreadB.Execute;
654+var
655+ i: Integer;
656+begin
657+ Counter:=0;
658+ while not Application.Terminated do begin
659+ // B: Pracuje ...
660+ Sleep(1500);
661+ inc(Counter);
662+ // wybudza A
663+ RtlEventSetEvent(Form1.ThreadA.WaitForB);
664+ end;
665+end;
666+```
667+
668+> **Note:** RtlEventSetEvent można wywołać przed RtlEventWaitFor. Wtedy RtlEventWaitFor natychmiast powróci. Użyj RLeventResetEvent, aby wyczyścić flagę.
669+
670+## Wątek potomny, rozwidlenie (fork)
671+
672+Podczas rozwidlenia w aplikacji wielowątkowej należy pamiętać, że wszelkie wątki utworzone i uruchomione PRZED wywołaniem fork (lub fpFork) NIE będą uruchomione w procesie potomnym. Jak stwierdzono na stronie podręcznika fork(), wszelkie wątki, które działały przed wywołaniem fork, będą miały stan niezdefiniowany.
673+
674+Należy więc pamiętać o wszelkich wątkach inicjujących się przed wywołaniem fork (w tym w sekcji inicjowania). NIE będą działać.
675+
676+## Procedury/pętle równoległe
677+
678+Szczególnym przypadkiem wielowątkowości jest równoległe uruchamianie pojedynczej procedury. Zobacz [[Parallel procedures|Procedury równoległe]].
679+
680+## Obliczenia rozproszone
681+
682+Następnym wyższym krokiem po wielowątkowości jest uruchamianie wątków na wielu komputerach.
683+
684+- Do komunikacji można użyć jednego z pakietów TCP, takich jak synapse, lnet lub indy. Daje to maksymalną elastyczność i jest używane głównie w przypadku luźno połączonych aplikacji Klient/Serwer.
685+- Możesz użyć bibliotek przekazujących komunikaty, takich jak [[MPICH]], które są używane do HPC (High Performance Computing) w klastrach.
686+
687+## Wątki zewnętrzne
688+
689+Aby system wątków Free Pascal działał poprawnie, każdy nowo utworzony wątek FPC musi zostać zainicjowany (dokładniej muszą być zainicjowane: wyjątek, system I/O i system zmiennych dla wątków, aby działały zmienne wątków i stos). Jest to wykonywane w pełni automatycznie, jeśli używasz BeginThread (lub pośrednio za pomocą klasy TThread). Jeśli jednak używasz wątków, które zostały utworzone bez BeginThread (tj. wątków zewnętrznych), może być wymagana dodatkowa praca (obecnie). Wątki zewnętrzne obejmują również te, które zostały utworzone w zewnętrznych bibliotekach C (.DLL/.so).
690+
691+Kwestie do rozważenia podczas korzystania z wątków zewnętrznych (mogą nie być potrzebne we wszystkich lub przyszłych wersjach kompilatora):
692+
693+- Nie używaj w ogóle wątków zewnętrznych - używaj wątków FPC. Jeśli możesz uzyskać kontrolę nad sposobem tworzenia wątku, utwórz wątek samodzielnie za pomocą BeginThread.
694+
695+Jeśli konwencja wywoływania nie pasuje (np. jeśli oryginalna funkcja wątku wymaga konwencji wywoływania cdecl, ale BeginThread wymaga konwencji Pascala, utwórz rekord, w którym przechowasz oryginalną wymaganą funkcję wątku i wywołaj tę funkcję w funkcji wątku Pascala:
696+
697+``` pascal
698+type
699+ TCdeclThreadFunc = function (user_data:Pointer):Pointer;cdecl;
700+
701+ PCdeclThreadFuncData = ^TCdeclThreadFuncData;
702+ TCdeclThreadFuncData = record
703+ Func: TCdeclThreadFunc; //funkcja cdecl
704+ Data: Pointer; //oryginalne dane
705+ end;
706+
707+// Wątek Pascala wywołuje funkcję cdecl
708+function C2P_Translator(FuncData: pointer) : ptrint;
709+var
710+ ThreadData: TCdeclThreadFuncData;
711+begin
712+ ThreadData := PCdeclThreadFuncData(FuncData)^;
713+ Result := ptrint(ThreadData.Func(ThreadData.Data));
714+end;
715+
716+procedure CreatePascalThread;
717+var
718+ ThreadData: PCdeclThreadFuncData;
719+begin
720+ New(ThreadData);
721+ // to jest pożądana funkcja wątku cdecl
722+ ThreadData^.Func := func;
723+ ThreadData^.Data := user_data;
724+ // to tworzy wątek Pascala
725+ BeginThread(@C2P_Translator, ThreadData );
726+end;
727+```
728+
729+- Zainicjuj system wątków FPC, tworząc fikcyjny wątek. Jeśli nie utworzysz żadnego wątku Pascala w swojej aplikacji, system wątków nie zostanie zainicjowany (a zatem zmienne wątków nie będą działać, a zatem i stos nie będzie działać poprawnie).
730+
731+``` pascal
732+type
733+ tc = class(tthread)
734+ procedure execute;override;
735+ end;
736+
737+ procedure tc.execute;
738+ begin
739+ end;
740+
741+{ main program }
742+begin
743+ { zainicjować system wątków }
744+ with tc.create(false) do
745+ begin
746+ waitfor;
747+ free;
748+ end;
749+ { ... Twój kod wykonywany dalej }
750+end.
751+```
752+
753+(Po zainicjowaniu systemu wątków środowisko wykonawcze może ustawić zmienną systemową „IsMultiThread” na wartość true, która jest używana przez procedury FPC do wykonywania blokad tu i tam. Nie należy ustawiać tej zmiennej ręcznie.)
754+
755+- Jeśli z jakiegoś powodu to nie działa, wypróbuj ten kod w funkcji wątku zewnętrznego:
756+
757+``` pascal
758+function ExternalThread(param: Pointer): LongInt; stdcall;
759+var
760+ tm: TThreadManager;
761+begin
762+ GetThreadManager(tm);
763+ tm.AllocateThreadVars;
764+ InitThread(1000000); // dostosuj tutaj początkowy rozmiar stosu
765+
766+ { zrób tutaj coś w wątku ... }
767+
768+ Result:=0;
769+end;
770+```
771+
772+### Identyfikacja wątków zewnętrznych
773+
774+Czasami nawet nie wiesz, czy masz do czynienia z wątkami zewnętrznymi (np. czy jakaś biblioteka C wykonuje wywołanie zwrotne). To może pomóc w analizie takiej sytuacji:
775+
776+1\. Zapytaj system operacyjny o identyfikator bieżącego wątku podczas uruchamiania aplikacji
777+
778+``` pascal
779+GetCurrentThreadID() //windows;
780+GetThreadID() //Darwin/macOS;
781+TThreadID(pthread_self) //Linux;
782+```
783+
784+2\. Zapytaj ponownie o identyfikator bieżącego wątku wewnątrz funkcji wątku i porównaj to z wynikiem kroku 1.
785+
786+### Dodawanie opóźnień czasowych
787+
788+ThreadSwitch()
789+
790+> **Note:** Nie używaj sztuczki z uśpieniem w Windows *Sleep(0)* ponieważ nie będzie to działać na wszystkich platformach.
791+
792+## Zobacz także
793+
794+- [[Streaming components|Komponenty strumieniowe]]
795+- [[Manager Worker Threads System|System nadzorowania wątków roboczych]]
796+- [[Example of multi-threaded application: array of threads/pl|Przykład aplikacji wielowątkowej: tablicy wątków]]
797+- [[Parallel procedures|Procedury równoległe]]
798+- [[Main Loop Hooks|Petla gówna uchwytów]]
799+- [[Asynchronous Calls|Wywołanie asynchroniczne]]
800+- [Mutlithreading - The Delphi way](http://nickhodges.com/MultiThreadingInDelphi/ToC.html)
801+
802+---
803+*From the Free Pascal wiki, page "Multithreaded Application Tutorial/pl", as it stood on 2023-09-23 after a change by Slawek, 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