Difference between revisions of "Multithreaded Application Tutorial/pl"

From Lazarus wiki
Jump to navigationJump to search
m (→‎Klasa TThread: Button1Click)
(36 intermediate revisions by 3 users not shown)
Line 1: Line 1:
 
{{Multithreaded Application Tutorial}}
 
{{Multithreaded Application Tutorial}}
  
<center>'''Kurs tworzenia aplikacji wielowątkowych'''</center><br>
+
<div style="font-size:1.88em;margin:0.75em 0;">Kurs tworzenia aplikacji wielowątkowych</div>
  
 
== Wprowadzenie ==
 
== Wprowadzenie ==
This page will try to explain how to write and debug a multi-threaded application with Free Pascal and Lazarus. A multi-threaded application is one that creates two or more threads of execution that work at the same time. If you are new to multi-threading, please read the paragraph "Do you need multi-threading?" to determine whether it is really required; this may save you many headaches.
+
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.
  
One of the threads is called the Main Thread. The Main Thread is the one that is created by the Operating System once our application starts. The Main Thread '''must be''' the only thread that updates the components that interfaces with the user: otherwise, the application may hang.
+
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ć.
  
The main idea is that the application can do some processing in background in a second thread while the user can continue  working using the main thread.
+
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.
  
Another use of threads is just to have a better responding application. If you create an application, and when the user presses a button the application starts processing a big job... and while processing, the screen stops responding, and gives the user the impression that the application is frozen, a poor or misleading impression will be created. If the big job runs in a second thread, the application keeps responding (almost) as if it were idle. In this case it is a good idea, before starting the thread, to disable the buttons of the form to avoid the user starting more than one thread for the job.
+
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.
  
Another use of multi-threading may be a server application that is able to respond to many clients at the same time.
+
Jeszcze innym zastosowaniem wielowątkowości może być serwer, który jest w stanie dać odpowiedź wielu klientom, w tym samym czasie.
  
 
== Czy potrzebujesz wielowątkowości? ==
 
== Czy potrzebujesz wielowątkowości? ==
  
If you are new to multi-threading and you only want to make your application more responsive while your application performs moderately long-running tasks, then multi-threading may be more than is required.
+
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ń.
Multi-threaded applications are always more difficult to debug and they are often much more complex; in many cases you don't need multi-threading. A single thread is enough. If you can split up the time-consuming task into several smaller chunks, then instead you should use '''Application.ProcessMessages'''. This method allows the LCL to handle all waiting messages and returns.
+
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.
The central idea is to call Application.ProcessMessages at regular intervals during the execution of a long-running task to determine whether the user has clicked on something, or a progress indicator must be repainted, and so on.
 
  
For example: Reading a big file and process it.  
+
Przykład: Czytanie dużego pliku i jego przetwarzanie. Zobacz: examples/multithreading/singlethreadingexample1.lpi.
See examples/multithreading/singlethreadingexample1.lpi.
 
  
Multi-threading is only needed for
+
Wielowątkowość jest potrzebna tylko w przypadku
* blocking handles, like network communications
+
* używania uchwytów blokujących, takich jak w komunikacji sieci
* using multiple processors simultaneously (SMP)
+
* korzystania z wielu procesorów jednocześnie (SMP)
* algorithms and library calls that must be called through an API and as such cannot be split up into smaller parts.
+
* algorytmów i bibliotek, które muszą być wywoływane przez API i jako takie nie mogą być podzielone na mniejsze części.
  
If you want to use multi-threading to increase speed by using multiple processors simultaneously, check if your current program now use all 100% resources of 1 core CPU (for example, you program can actively use input-output operations, e.g. writing to file; this takes a lot of time, but doesn't load CPU; in this case you program will not be faster with multiple threads). Also check if optimisation level is set to maximum (3). When I switched optimisation level from 1 to 3, my program became about 5 times faster.
+
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.
  
== Moduły potrzebne do tworzenia aplikacji wielowątkowych ==
+
== Moduły potrzebne do tworzenia aplikacji wielowątkowej ==
You don´t need any special unit for this to work with Windows.
+
Nie potrzebujesz żadnych specjalnych modułów do tego, aby pracować z systemem Windows.
However with Linux, Mac OS X and FreeBSD, you need the cthreads unit and it ''must'' be the first used unit of the project (the program source, usually the .lpr file)!
+
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)!
  
So, your Lazarus application code should look like:
+
Dlatego twój kod aplikacji Lazarusa powinien wyglądać tak:
  
<syntaxhighlight>
+
<syntaxhighlight lang="pascal">
 
program MyMultiThreadedProgram;
 
program MyMultiThreadedProgram;
 
{$mode objfpc}{$H+}
 
{$mode objfpc}{$H+}
Line 42: Line 40:
 
{$ifdef unix}
 
{$ifdef unix}
 
   cthreads,
 
   cthreads,
   cmem, // the c memory manager is on some systems much faster for multi-threading
+
   cmem, // menedżer pamięci c jest w niektórych systemach znacznie szybszy dla aplikacji wielowątkowych
 
{$endif}
 
{$endif}
   Interfaces, // this includes the LCL widgetset
+
   Interfaces, // to zawiera widżety LCL
 
   Forms
 
   Forms
   { you can add units here },
+
   { tu możesz dodać następne moduły },
 
</syntaxhighlight>
 
</syntaxhighlight>
  
If you forget this and you use TThread you will get this error on startup:
+
Jeśli o tym zapomnisz i użyjesz TThread, otrzymasz następujący błąd podczas uruchamiania:
 
   This binary has no thread support compiled in.
 
   This binary has no thread support compiled in.
 
   Recompile the application with a thread-driver in the program uses clause before other units using thread.
 
   Recompile the application with a thread-driver in the program uses clause before other units using thread.
  
{{Note|If you get a Linker error about "mcount" not found. Then you use some unit that contains some multithreaded code and you need to add the cthreads unit or use smart linking.}}
+
{{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]].}}
{{Note|If you get the error: "Project raised exception class 'RunError(232)'" in procedure SYSTEM_NOTHREADERROR then your code requires threading and you need to add the cthreads unit.}}
+
{{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.}}
 +
 
 +
== Przykład w czystym FPC ==
 +
Poniższy kod przedstawia bardzo prosty przykład. Testowany z FPC 3.0.4 na Win7.
 +
<syntaxhighlight lang="pascal">
 +
Program ThreadTest;
 +
{ przetestuj możliwość aplikacji wielowątkowej }
 +
{
 +
      OUTPUT
 +
wątek 1 uruchomiony
 +
wątek 1 zmienna thri 0 Długość(s)= 1
 +
wątek 1 zmienna thri 1 Długość(s)= 2
 +
wątek 1 zmienna thri 2 Długość(s)= 3
 +
wątek 1 zmienna thri 3 Długość(s)= 4
 +
wątek 1 zmienna thri 4 Długość(s)= 5
 +
wątek 1 zmienna thri 5 Długość(s)= 6
 +
wątek 1 zmienna thri 6 Długość(s)= 7
 +
wątek 1 zmienna thri 7 Długość(s)= 8
 +
wątek 1 zmienna thri 8 Długość(s)= 9
 +
wątek 1 zmienna thri 9 Długość(s)= 10
 +
wątek 1 zmienna thri 10 Długość(s)= 11
 +
wątek 1 zmienna thri 11 Długość(s)= 12
 +
wątek 1 zmienna thri 12 Długość(s)= 13
 +
wątek 1 zmienna thri 13 Długość(s)= 14
 +
wątek 1 zmienna thri 14 Długość(s)= 15
 +
wątek 2 uruchomiony
 +
wątek 3 uruchomiony
 +
wątek 1 zmienna thri 15 Długość(s)= 16
 +
wątek 2 zmienna thri 0 Długość(s)= 1
 +
wątek 3 zmienna thri 0 Długość(s)= 1
 +
wątek 1 zmienna thri 16 Długość(s)= 17
 +
...
 +
...
 +
wątek 5 zmienna thri 997 Długość(s)= 998
 +
wątek 5 zmienna thri 998 Długość(s)= 999
 +
wątek 5 zmienna thri 999 Długość(s)= 1000
 +
wątek 5 zakończony
 +
wątek 10 zmienna thri 828 Długość(s)= 829
 +
wątek 9 zmienna thri 675 Długość(s)= 676
 +
wątek 4 zmienna thri 656 Długość(s)= 657
 +
wątek 10 zmienna thri 829 Długość(s)= 830
 +
wątek 9 zmienna thri 676 Długość(s)= 677
 +
wątek 9 zmienna thri 677 Długość(s)= 678
 +
wątek 10 zmienna thri 830 Długość(s)= 831
 +
wątek 10 zmienna thri 831 Długość(s)= 832
 +
wątek 10 zmienna thri 832 Długość(s)= 833
 +
wątek 10 zmienna thri 833 Długość(s)= 834
 +
wątek 10 zmienna thri 834 Długość(s)= 835
 +
wątek 10 zmienna thri 835 Długość(s)= 836
 +
wątek 10 zmienna thri 836 Długość(s)= 837
 +
wątek 10 zmienna thri 837 Długość(s)= 838
 +
wątek 10 zmienna thri 838 Długość(s)= 839
 +
wątek 10 zmienna thri 839 Długość(s)= 840
 +
wątek 9 zmienna thri 678 Długość(s)= 679
 +
...
 +
...
 +
wątek 4 zmienna thri 994 Długość(s)= 995
 +
wątek 4 zmienna thri 995 Długość(s)= 996
 +
wątek 4 zmienna thri 996 Długość(s)= 997
 +
wątek 4 zmienna thri 997 Długość(s)= 998
 +
wątek 4 zmienna thri 998 Długość(s)= 999
 +
wątek 4 zmienna thri 999 Długość(s)= 1000
 +
wątek 4 zakończony
 +
10
 +
 
 +
}
 +
 
 +
uses
 +
  {$ifdef unix}cthreads, {$endif} sysutils;
 +
 
 +
const
 +
  threadcount = 10;
 +
  stringlen = 1000;
 +
 
 +
var
 +
  finished : longint;
 +
 
 +
threadvar
 +
  thri : ptrint;
 +
 
 +
function f(p : pointer) : ptrint;
 +
var
 +
  s : ansistring;
 +
begin
 +
  Writeln('wątek ',longint(p),' uruchomiony');
 +
  thri:=0;
 +
  while (thri<stringlen) do begin
 +
    s:=s+'1'; { stwórz opóźnienie }
 +
    writeln('wątek ',longint(p),' zmienna thri ',thri,' Długość(s)= ',length(s));
 +
  inc(thri);
 +
  end;
 +
  Writeln('wątek ',longint(p),' zakończony');
 +
  InterLockedIncrement(finished);
 +
  f:=0;
 +
end;
 +
 
 +
 
 +
var
 +
  i : longint;
 +
 
 +
Begin
 +
  finished:=0;
 +
  for i:=1 to threadcount do
 +
    BeginThread(@f,pointer(i));
 +
  while finished<threadcount do ;
 +
  Writeln(finished);
 +
End.
 +
 
 +
</syntaxhighlight>
  
 
== Klasa TThread ==
 
== Klasa TThread ==
  
The following example can be found in the examples/multithreading/ directory.
+
Poniższy przykład można znaleźć w katalogu examples/multithreading/.
  
To create a multi-threaded application, the easiest way is to use the TThread Class. This class permits the creation of an additional thread (alongside the main thread) in a simple way. Normally you are required to override only 2 methods: the Create constructor, and the Execute method.
+
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.
  
In the constructor, you will prepare the thread to run.  You will set the initial values of the variables or properties you need. The original constructor of TThread requires a parameter called Suspended. As you might expect, setting Suspended = True will prevent the thread starting automatically after the creation. 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 Start method is called.
+
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.
  
{{Note|Method Resume is [http://wiki.freepascal.org/User_Changes_2.4.4#TThread.Suspend_and_TThread.Resume_have_been_deprecated deprecated since FPC 2.4.4]. It is replaced by Start.}}
+
{{Note|Metoda Resume jest [http://wiki.freepascal.org/User_Changes_2.4.4#TThread.Suspend_and_TThread.Resume_have_been_deprecated przestarzała od FPC 2.4.4]. Została zastąpiona przez metodę Start.}}
  
As of FPC version 2.0.1 and later, TThread.Create also has an implicit parameter for Stack Size. 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.
+
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.
  
In the overridden Execute method you will write the code that will run on the thread.
+
W zastąpionej metodzie Execute napisz kod, który będzie uruchamiany w wątku.
  
The TThread class has one important property:  
+
Klasa TThread ma jedną ważną właściwość:
 
Terminated : boolean;
 
Terminated : boolean;
  
If the thread has a loop (and this is typical), the loop should be exited when Terminated is true (it is false by default). Within each pass, the value of Terminated must be checked, and if it is true then the loop should be exited as quickly as is appropriate, after any necessary cleanup. Bear in mind that the Terminate method does not do anything by default: the .Execute method must explicitly implement support for it to quit its job.
+
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. <del>Należy pamiętać, że metoda Terminate domyślnie nic nie robi: metoda .Execute musi jawnie zaimplementować obsługę, aby zakończyć swoje zadanie.</del> Metoda Terminate domyślnie ustawia jedynie właściwość Terminated na wartość '''True'''.
  
As we explained earlier, the thread should not interact with the visible components. Updates to visible components must be made within the context of the main thread.
+
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.
  
To do this, a TThread method called Synchronize exists. Synchronize requires a method within the thread (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 be called from the main thread, and then the thread execution will be resumed.
+
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.
  
The exact working of Synchronize depends on the platform, but basically it does this:
+
Dokładne działanie metody Synchronize zależy od platformy, ale zasadniczo wykonuje ona:
* it posts a message onto the main message queue and goes to sleep
+
* wysyła wiadomość do głównej kolejki wiadomości i zasypia
* 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.
+
* 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.
* after the main thread executed MyMethod, it wakes the sleeping Thread and processes the next message
+
* po wykonaniu przez główny wątek MyMethod, budzi ona uśpiony wątek i przetwarza następną wiadomość
* the Thread then continues.
+
* następnie wątek jest kontynuowany.
  
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.
+
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.
  
Example:
+
Przykład:
  
<syntaxhighlight> Type
+
<syntaxhighlight lang="pascal">
 +
  Type
 
     TMyThread = class(TThread)
 
     TMyThread = class(TThread)
 
     private
 
     private
Line 99: Line 206:
 
       Constructor Create(CreateSuspended : boolean);
 
       Constructor Create(CreateSuspended : boolean);
 
     end;
 
     end;
 
 
   constructor TMyThread.Create(CreateSuspended : boolean);
 
   constructor TMyThread.Create(CreateSuspended : boolean);
 
   begin
 
   begin
 +
    inherited Create(CreateSuspended);
 
     FreeOnTerminate := True;
 
     FreeOnTerminate := True;
    inherited Create(CreateSuspended);
 
 
   end;
 
   end;
  
 
   procedure TMyThread.ShowStatus;
 
   procedure TMyThread.ShowStatus;
   // this method is executed by the mainthread and can therefore access all GUI elements.
+
   // ta metoda jest wykonywana przez główny wątek i dlatego może uzyskać dostęp do wszystkich elementów GUI.
 
   begin
 
   begin
 
     Form1.Caption := fStatusText;
 
     Form1.Caption := fStatusText;
Line 119: Line 225:
 
     Synchronize(@Showstatus);
 
     Synchronize(@Showstatus);
 
     fStatusText := 'TMyThread Running...';
 
     fStatusText := 'TMyThread Running...';
     while (not Terminated) and ([any condition required]) do
+
     while (not Terminated) and ([inne wymagane warunki]) do
 
       begin
 
       begin
 
         ...
 
         ...
         [here goes the code of the main thread loop]
+
         [tutaj jest kod głównej pętli wątku]
 
         ...
 
         ...
 
         if NewStatus <> fStatusText then
 
         if NewStatus <> fStatusText then
Line 133: Line 239:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
On the application,
+
W aplikacji:
  
<syntaxhighlight>
+
<syntaxhighlight lang="pascal">
 
   var
 
   var
 
     MyThread : TMyThread;
 
     MyThread : TMyThread;
 
   begin
 
   begin
     MyThread := TMyThread.Create(True); // This way it doesn't start automatically
+
     MyThread := TMyThread.Create(True); // W ten sposób nie uruchamia się automatycznie
 
     ...
 
     ...
     [Here the code initialises anything required before the threads starts executing]
+
     [Tutaj kod inicjuje wszystko, co jest wymagane, zanim wątki zaczną się wykonywać]
 
     ...
 
     ...
 
     MyThread.Start;
 
     MyThread.Start;
 
   end;</syntaxhighlight>
 
   end;</syntaxhighlight>
  
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:
+
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:
  
<syntaxhighlight> Type
+
<syntaxhighlight lang="pascal">
 +
  Type
 
     TShowStatusEvent = procedure(Status: String) of Object;
 
     TShowStatusEvent = procedure(Status: String) of Object;
  
Line 165: Line 272:
 
   constructor TMyThread.Create(CreateSuspended : boolean);
 
   constructor TMyThread.Create(CreateSuspended : boolean);
 
   begin
 
   begin
 +
    inherited Create(CreateSuspended);
 
     FreeOnTerminate := True;
 
     FreeOnTerminate := True;
    inherited Create(CreateSuspended);
 
 
   end;
 
   end;
  
 
   procedure TMyThread.ShowStatus;
 
   procedure TMyThread.ShowStatus;
   // this method is executed by the mainthread and can therefore access all GUI elements.
+
   // ta metoda jest wykonywana przez główny wątek i dlatego może uzyskać dostęp do wszystkich elementów GUI.
 
   begin
 
   begin
 
     if Assigned(FOnShowStatus) then
 
     if Assigned(FOnShowStatus) then
Line 185: Line 292:
 
     Synchronize(@Showstatus);
 
     Synchronize(@Showstatus);
 
     fStatusText := 'TMyThread Running...';
 
     fStatusText := 'TMyThread Running...';
     while (not Terminated) and ([any condition required]) do
+
     while (not Terminated) and ([inne wymagane warunki]) do
 
       begin
 
       begin
 
         ...
 
         ...
         [here goes the code of the main thread loop]
+
         [tutaj jest kod głównej pętli wątku]
 
         ...
 
         ...
 
         if NewStatus <> fStatusText then
 
         if NewStatus <> fStatusText then
Line 199: Line 306:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
On the application,
+
W aplikacji:
  
<syntaxhighlight> Type
+
<syntaxhighlight lang="pascal">
 +
  Type
 
     TForm1 = class(TForm)
 
     TForm1 = class(TForm)
 
       Button1: TButton;
 
       Button1: TButton;
 
       Label1: TLabel;
 
       Label1: TLabel;
 +
      procedure Button1Click(Sender: TObject);
 
       procedure FormCreate(Sender: TObject);
 
       procedure FormCreate(Sender: TObject);
 
       procedure FormDestroy(Sender: TObject);
 
       procedure FormDestroy(Sender: TObject);
Line 226: Line 335:
 
     MyThread.Terminate;
 
     MyThread.Terminate;
  
     // FreeOnTerminate is true so we should not write:
+
     // FreeOnTerminate ma wartość true więc nie powinniśmy pisać:
 
     // MyThread.Free;
 
     // MyThread.Free;
 
     inherited;
 
     inherited;
Line 245: Line 354:
 
=== Kontrola stosu w systemie Windows ===
 
=== Kontrola stosu w systemie Windows ===
  
There is a potential headache in Windows with Threads if you use the -Ct (stack check) switch.
+
Może wystąpić potencjalny problem w systemie Windows, jeśli używasz przełącznika -Ct (sprawdzanie stosu).
For reasons not so clear the stack check will "trigger" on any TThread.Create if you use the default stack size.
+
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.
The only work-around for the moment is to simply not use -Ct switch. Note that it does NOT cause an exception in
+
W tej chwili jedynym rozwiązaniem jest po prostu nieużywanie przełącznika -Ct. Zauważ, że NIE powoduje to wyjątku w
the main thread, but in the newly created one. This "looks" like if the thread was never started.
+
głównym wątku, ale w nowo utworzonym. To „wygląda” tak, jakby wątek nigdy nie został uruchomiony.
 +
 
 +
Poprawny kod do sprawdzenia tego i innych wyjątków, które mogą wystąpić podczas tworzenia wątków, to:
  
A good code to check for this and other exceptions which can occur in thread creation is:
+
<syntaxhighlight lang="pascal">
 +
MyThread := TThread.Create(False);
  
<syntaxhighlight>MyThread := TThread.Create(False);
 
 
if Assigned(MyThread.FatalException) then
 
if Assigned(MyThread.FatalException) then
 
   raise MyThread.FatalException;</syntaxhighlight>
 
   raise MyThread.FatalException;</syntaxhighlight>
  
This code will assure that any exception which occurred during thread creation will be raised in your main thread.
+
Ten kod zapewni, że każdy wyjątek, który wystąpił podczas tworzenia wątku, zostanie zgłoszony w głównym wątku.
  
 
=== Wielowątkowość w pakietach ===
 
=== Wielowątkowość w pakietach ===
Packages which uses multi-threading should add the '''-dUseCThreads''' flag to the custom usage options. Open the package editor of the package, then Options > Usage > Custom and add ''-dUseCThreads''. This will define this flag to all projects and packages using this package, including the IDE. The IDE and all new applications created by the IDE have already the following code in their .lpr file:
+
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:
<syntaxhighlight>
+
 
 +
<syntaxhighlight lang="pascal">
 
uses
 
uses
 
   {$IFDEF UNIX}{$IFDEF UseCThreads}
 
   {$IFDEF UNIX}{$IFDEF UseCThreads}
 
   cthreads,
 
   cthreads,
   cmem, // the c memory manager is on some systems much faster for multi-threading
+
   cmem, // Menedżer pamięci c jest w niektórych systemach znacznie szybszy w przypadku wielowątkowości
 
   {$ENDIF}{$ENDIF}
 
   {$ENDIF}{$ENDIF}
 
</syntaxhighlight>
 
</syntaxhighlight>
Line 270: Line 382:
 
=== Moduł Heaptrc ===
 
=== Moduł Heaptrc ===
  
You can not use the -gh switch with the ''cmem'' unit. The -gh switch uses the heaptrc unit, which extends the heap manager. Therefore the '''heaptrc''' unit must be used '''after''' the '''cmem''' unit.
+
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 '''after''' module '''cmem'''.
  
<syntaxhighlight>uses
+
<syntaxhighlight lang="pascal">
 +
uses
 
   {$IFDEF UNIX}{$IFDEF UseCThreads}
 
   {$IFDEF UNIX}{$IFDEF UseCThreads}
 
   cthreads,
 
   cthreads,
   cmem, // the c memory manager is on some systems much faster for multi-threading
+
   cmem, // Menedżer pamięci c jest w niektórych systemach znacznie szybszy w przypadku wielowątkowości
 
   {$ENDIF}{$ENDIF}
 
   {$ENDIF}{$ENDIF}
 
   heaptrc,</syntaxhighlight>
 
   heaptrc,</syntaxhighlight>
 +
 +
=== Initialization and Finalization ===
 +
 +
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.
 +
 +
'''Uwaga''': Używanie AfterConstruction, gdy CreateSuspended=false jest niebezpieczne, ponieważ wątek już zaczął działać.
 +
 +
Z drugiej strony, destruktor może służyć do finalizowania zasobów obiektu.
 +
 +
<syntaxhighlight lang="pascal">
 +
type
 +
  TMyThread = class(TThread)
 +
  private
 +
    fRTLEvent: PRTLEvent;
 +
  public
 +
    procedure Create(SomeData: TSomeObject); override;
 +
    destructor Destroy; override;
 +
  end;
 +
 +
procedure TMyThread.Create(SomeData: TSomeObject; CreateSuspended: boolean);
 +
begin
 +
  // przykład: skonfiguruj zdarzenia, sekcje krytyczne i inne zasoby, takie jak pliki lub połączenia z bazami danych
 +
  RTLEventCreate(fRTLEvent);
 +
  inherited Create(CreateSuspended);
 +
end;
 +
 +
destructor TMyThread.Destroy;
 +
begin
 +
  RTLeventDestroy(fRTLEvent);
 +
  inherited Destroy;
 +
end;
 +
</syntaxhighlight>
 +
 +
=== Program bez LCL ===
 +
 +
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.
  
 
== Wsparcie dla SMP ==
 
== Wsparcie dla SMP ==
The good news is that if your application works properly multi-threaded this way, it is already SMP enabled!
+
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!
  
 
== Debugowanie aplikacji wielowątkowych w Lazarusie ==
 
== Debugowanie aplikacji wielowątkowych w Lazarusie ==
The debugging on Lazarus requires GDB and is rapidly becoming more and more fully featured and stable. However, there still exists a few Linux distributions with some problems.  
+
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.
  
 
=== Wyjście debuggera ===
 
=== Wyjście debuggera ===
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.
+
W jednowątkowej aplikacji możesz po prostu pisać do konsoli/terminala/gdziekolwiek, a kolejność wierszy jest taka sama, jak zostały one napisane.
In multi-threaded 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 necessarily written in that order. It can even happen, that a thread writes its output, while the other thread is writing a line.While under linux (maybe) you'll get proper DebugLn() output, under win32 you can get exceptions (probably DiskFull) because of DebugLn() usage outside of main thread.So, to avoid headaches use DebugLnThreadLog() mentioned below.
+
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.
  
The LCLProc unit contains several functions, to let each thread write to its own log file:
+
Moduł LCLProc zawiera kilka funkcji, które pozwalają każdemu wątkowi zapisywać do własnego pliku dziennika:
<syntaxhighlight> procedure DbgOutThreadLog(const Msg: string); overload;
+
<syntaxhighlight lang="pascal">
 +
  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;</syntaxhighlight>
+
   procedure DebuglnThreadLog; overload;
 +
</syntaxhighlight>
  
For example:
+
Na przykład:
Instead of ''writeln('Some text ',123);'' use
+
Zamiast writeln('Jakiś tekst', 123); użyj
   DebuglnThreadLog(['Some text ',123]);
+
   DebuglnThreadLog(['Jakiś tekst ',123]);
  
This will append a line 'Some text 123' to '''Log<PID>.txt''', where <PID> is the process ID of the current thread.
+
Spowoduje to dodanie linii 'Jakiś tekst 123' do '''Log<PID>.txt''', gdzie <PID> jest identyfikatorem procesu bieżącego wątku.
  
It is a good idea to remove the log files before each run:
+
Dobrym pomysłem jest usunięcie plików dziennika przed każdym uruchomieniem:
 
   rm -f Log* && ./project1
 
   rm -f Log* && ./project1
  
 
=== Linux ===
 
=== Linux ===
If you try to debug a multi-threaded application on Linux, you will have one big problem: the Desktop Manager on X server can hang. This happens for instance when the application has captured the mouse/keyboard and was paused by gdb and the X server waits for your application. When that happens you can simply log in from another computer and kill the gdb or exit out of that session by pressing CTRL+ALT+F3 and kill gdb. Alternatively you can restart the window manager: enter sudo /etc/init.d/gdm restart. This will restart the desktop manager and get you back into your desktop.
+
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.
  
Since it depends where gdb stops your program in some cases some tricks may help: for Ubuntu x64 set the Project options for debugging required extra information file...
+
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...
  
   Project Options -> Compiler Options -> Linking -> Debugging: Check Use external gdb debug symbols file (-Xg).
+
   Projekt -> Opcje projektu -> Opcje kompilatora -> Odpluskwianie, i zaznacz: Use external gdb debug symbols file (-Xg).
  
The other option is to open anotner X desktop, run the IDE/gdb on one and the application on the other, so that only the test desktop freezes. Create a new instance of X with:
+
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ą:
  
 
   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 [http://www.slackware.com Slackware]).
+
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 [http://www.slackware.com Slackware]).
  
Then you could, if you want, create a desktop session on the X started with:
+
Następnie możesz, jeśli chcesz, utworzyć sesję pulpitu na X, za pomocą polecenia:
  
 
   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.
+
Następnie w Lazarusie w oknie dialogowym parametrów uruchamiania projektu (Uruchom -> Uruchom z parametrami -> Ekran) zaznacz opcję „Użyj ekranu” i wpisz :1.
 
 
Now the application will run on the second 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.
+
Teraz aplikacja będzie działać na drugim serwerze X i będziesz mógł ją debugować na pierwszym.
  
 +
Zostało to przetestowane z Free Pascal 2.0 i Lazarus 0.9.10 w systemie Linux.
  
 
----
 
----
  
Instead of creating a new X session, one can use [http://en.wikipedia.org/wiki/Xnest Xnest]. Xnest is a X session on a window. Using it X server didn't lock while debugging threads, and it's much easier to debug without keeping changing terminals.
+
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.
  
The command line to run Xnest is
+
Linia poleceń do uruchomienia Xnest to
  
 
   Xnest :1 -ac
 
   Xnest :1 -ac
  
to create a X session on :1, and disabling access control.
+
tworzy sesję X na :1 i wyłącza kontrolę dostępu.
  
== Modyfikowanie widżetów (in. kontrolek, formatek) interfejsu w Lazarusie ==
+
== Interfejsy widżetów Lazarusa ==
The win32, the gtk and the carbon interfaces support multi-threading. This means, TThread, critical sections and Synchronize work. But they are not thread safe. This means only one thread at a time can access the LCL. And since the main thread should never wait for another thread, it means only the main thread is allowed to access the LCL, which means anything that has to do with TControl, Application and LCL widget handles.
+
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.
There are some thread safe functions in the LCL. For example most of the functions in the FileUtil unit are thread safe.
+
W LCL jest kilka funkcji bezpiecznych dla wątków. Na przykład większość funkcji w module FileUtil jest bezpieczna wątkowo.
  
 
=== Użycie SendMessage/PostMessage do komunikacji pomiędzy wątkami ===
 
=== Użycie SendMessage/PostMessage do komunikacji pomiędzy wątkami ===
  
Only one thread in an application should call LCL APIs, usually the main thread. Other threads can make use of the LCL through a number of indirect methods, one good option being the usage of SendMessage or PostMessage. LCLIntf.SendMessage and LCLIntf.PostMessage will post a message directed to a window in the message pool of the application.
+
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.
  
See also the documentation for these routines:  
+
Zobacz także dokumentację tych procedur:
 
*[[doc:lcl/lclintf/sendmessage.html|SendMessage]]
 
*[[doc:lcl/lclintf/sendmessage.html|SendMessage]]
 
*[[doc:lcl/lclintf/postmessage.html|PostMessage]]
 
*[[doc:lcl/lclintf/postmessage.html|PostMessage]]
  
The difference between SendMessage and PostMessage is the way that they return control to the calling thread. With SendMessage control is not returned until the window that the message was sent to has completed processing the sent message, however with PostMessage control is returned immediately.
+
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.
 +
 
 +
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.
  
Here is an example of how a secondary thread could send text to be displayed in an LCL control to the main thread:
+
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:
  
<syntaxhighlight>
+
<syntaxhighlight lang="pascal">
 
const
 
const
 
   WM_GOT_ERROR          = LM_USER + 2004;
 
   WM_GOT_ERROR          = LM_USER + 2004;
Line 370: Line 522:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
And an example of how to handle this message from a window:
+
I przykład, jak obsłużyć tę wiadomość z okna:
  
<syntaxhighlight>
+
<syntaxhighlight lang="pascal">
 
const
 
const
 
   WM_GOT_ERROR          = LM_USER + 2004;
 
   WM_GOT_ERROR          = LM_USER + 2004;
Line 408: Line 560:
 
   Item.SubItems.Add(MsgPasStr);
 
   Item.SubItems.Add(MsgPasStr);
 
   Item.MakeVisible(False);
 
   Item.MakeVisible(False);
  //f/TrayControl.SetError(MsgPasStr);
+
 
 +
// Po którym następuje coś takiego
 +
 
 +
  TrayControl.SetError(MsgPasStr);
 +
  StrDispose(MsgStr)
 
end;
 
end;
  
Line 415: Line 571:
  
 
== Sekcje krytyczne ==
 
== Sekcje krytyczne ==
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. A critical section needs to be created/initialized before it can be used and be freed when it is not needed anymore.
+
''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.
  
Critical sections are normally used this way:
+
Sekcje krytyczne zwykle są używane w ten sposób:
  
Declare the section (globally for all threads which should access the section):
+
Deklaracja sekcji (globalnie dla wszystkich wątków, które powinny mieć dostęp do tej sekcji):
 
   MyCriticalSection: TRTLCriticalSection;
 
   MyCriticalSection: TRTLCriticalSection;
  
Create the section:
+
Utworzenie sekcji:
 
   InitializeCriticalSection(MyCriticalSection);
 
   InitializeCriticalSection(MyCriticalSection);
  
Run some threads. Doing something exclusively
+
Uruchomienie kilka wątków. Robienie czegoś na wyłączność:
<syntaxhighlight>
+
 
 +
<syntaxhighlight lang="pascal">
 
EnterCriticalSection(MyCriticalSection);
 
EnterCriticalSection(MyCriticalSection);
 
try
 
try
   // access some variables, write files, send some network packets, etc
+
   // uzyskaj dostęp do zmiennych, zapisz pliki, wyślij pakiety sieciowe itp.
 
finally
 
finally
 
   LeaveCriticalSection(MyCriticalSection);
 
   LeaveCriticalSection(MyCriticalSection);
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
After all threads terminated, free it:
+
Po zakończeniu wszystkich wątków zwolnij sekcję krytyczną:
 
   DeleteCriticalSection(MyCriticalSection);
 
   DeleteCriticalSection(MyCriticalSection);
  
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.
+
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.
  
For example: 5 threads incrementing a counter.
+
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).
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.
+
Na przykład: 5 wątków zwiększających licznik.
 +
Zobacz lazarus/examples/multithreading/criticalsectionexample1.lpi
  
 +
{{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.}}
  
 
=== Współdzielenie zmiennych ===
 
=== Współdzielenie zmiennych ===
If some threads share a variable, that is read only, then there is nothing to worry about. Just read it.
+
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ść.
But if one or several threads changes the variable, then you must make sure, that only one thread accesses the variables at a time.
+
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.
  
For example: 5 threads incrementing a counter.
+
Na przykład: 5 wątków zwiększających licznik.
See lazarus/examples/multithreading/criticalsectionexample1.lpi
+
Zobacz lazarus/examples/multithreading/criticalsectionexample1.lpi
  
 
== Oczekiwanie na inny wątek ==
 
== Oczekiwanie na inny wątek ==
If a thread A needs a result of another thread B, it must wait, till B has finished.  
+
Jeśli wątek A potrzebuje wyniku innego wątku B, musi on poczekać, B zakończy pracę.
  
'''Important:''' The main thread should never wait for another thread. Instead use Synchronize (see above).
+
'''Ważne:''' Główny wątek nigdy nie powinien czekać na kolejny wątek. Zamiast tego użyj opcji Synchronizuj (patrz wyżej).
  
See for an example: lazarus/examples/multithreading/waitforexample1.lpi
+
Zobacz przykład: lazarus/examples/multithreading/waitforexample1.lpi
  
<syntaxhighlight>{ TThreadA }
+
<syntaxhighlight lang="pascal">
 +
{ TThreadA }
  
 
procedure TThreadA.Execute;
 
procedure TThreadA.Execute;
 
begin
 
begin
 
   Form1.ThreadB:=TThreadB.Create(false);
 
   Form1.ThreadB:=TThreadB.Create(false);
   // create event
+
   // Utwórz zdarzenie
 
   WaitForB:=RTLEventCreate;
 
   WaitForB:=RTLEventCreate;
 
   while not Application.Terminated do begin
 
   while not Application.Terminated do begin
     // wait infinitely (until B wakes A)
+
     // czekaj w nieskończoność (B obudzi A)
 
     RtlEventWaitFor(WaitForB);
 
     RtlEventWaitFor(WaitForB);
 
     writeln('A: ThreadB.Counter='+IntToStr(Form1.ThreadB.Counter));
 
     writeln('A: ThreadB.Counter='+IntToStr(Form1.ThreadB.Counter));
Line 481: Line 640:
 
   Counter:=0;
 
   Counter:=0;
 
   while not Application.Terminated do begin
 
   while not Application.Terminated do begin
     // B: Working ...
+
     // B: Pracuje ...
 
     Sleep(1500);
 
     Sleep(1500);
 
     inc(Counter);
 
     inc(Counter);
     // wake A
+
     // wybudza A
 
     RtlEventSetEvent(Form1.ThreadA.WaitForB);
 
     RtlEventSetEvent(Form1.ThreadA.WaitForB);
 
   end;
 
   end;
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
{{Note| RtlEventSetEvent can be called before RtlEventWaitFor. Then RtlEventWaitFor will return immediately. Use RTLeventResetEvent to clear a flag.}}
+
{{Note| RtlEventSetEvent można wywołać przed RtlEventWaitFor. Wtedy RtlEventWaitFor natychmiast powróci. Użyj RLeventResetEvent, aby wyczyścić flagę.}}
  
== Wątek potomny (fork) ==
+
== Wątek potomny, rozwidlenie (fork) ==
When forking in a multi-threaded 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.
+
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.
  
So be aware of any threads initializing before the call (including on the initialization section). They will NOT work.
+
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ć.
  
 
== Procedury/pętle równoległe ==
 
== Procedury/pętle równoległe ==
A special case of multi threading is running a single procedure in parallel. See [[Parallel procedures]].
+
Szczególnym przypadkiem wielowątkowości jest równoległe uruchamianie pojedynczej procedury. Zobacz [[Parallel procedures|Procedury równoległe]].
  
 
== Obliczenia rozproszone ==
 
== Obliczenia rozproszone ==
The next higher steps after multi threading is running the threads on multiple machines.  
+
Następnym wyższym krokiem po wielowątkowości jest uruchamianie wątków na wielu komputerach.
* 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.
+
* 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.
* You can use message passing libraries like [[MPICH]], which are used for HPC (High Performance Computing) on clusters.
+
* Możesz użyć bibliotek przekazujących komunikaty, takich jak [[MPICH]], które są używane do HPC (High Performance Computing) w klastrach.
 
 
 
 
  
 
== Wątki zewnętrzne ==
 
== Wątki zewnętrzne ==
To make Free Pascal's threading system work properly, each newly created FPC thread needs to be initialized (more exactly, the exception, I/O system and threadvar system per thread needs to be initialized so threadvars and heap are working). That is fully automatically done for you if you use BeginThread (or indirectly by using the TThread class). However, if you use threads that were created without BeginThread (i.e. external threads), additional work (currently) might be required. External threads also include those that were created in external C libraries (.DLL/.so).
+
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).
  
 +
Kwestie do rozważenia podczas korzystania z wątków zewnętrznych (mogą nie być potrzebne we wszystkich lub przyszłych wersjach kompilatora):
  
Things to consider when using external threads (might not be needed in all or future compiler versions):
+
* 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.
  
* Do not use external threads at all - use FPC threads. If can you can get control over how the thread is created, create the thread by yourself by using BeginThread.
+
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:
  
If the calling convention doesn't fit (e.g. if your original thread function needs cdecl calling convention but BeginThread needs pascal convention, create a record, store the original required thread function in it, and call that function in your pascal thread function: 
+
<syntaxhighlight lang="pascal">
 
+
type
<syntaxhighlight>type
 
 
  TCdeclThreadFunc = function (user_data:Pointer):Pointer;cdecl;
 
  TCdeclThreadFunc = function (user_data:Pointer):Pointer;cdecl;
  
 
  PCdeclThreadFuncData = ^TCdeclThreadFuncData;
 
  PCdeclThreadFuncData = ^TCdeclThreadFuncData;
 
  TCdeclThreadFuncData = record
 
  TCdeclThreadFuncData = record
   Func: TCdeclThreadFunc;  //cdecl function
+
   Func: TCdeclThreadFunc;  //funkcja cdecl
   Data: Pointer;          //original data
+
   Data: Pointer;          //oryginalne dane
 
  end;
 
  end;
  
// The Pascal thread calls the cdecl function
+
// Wątek Pascala wywołuje funkcję cdecl
 
function C2P_Translator(FuncData: pointer) : ptrint;
 
function C2P_Translator(FuncData: pointer) : ptrint;
 
var
 
var
Line 539: Line 696:
 
begin
 
begin
 
   New(ThreadData);
 
   New(ThreadData);
   // this is the desired cdecl thread function
+
   // to jest pożądana funkcja wątku cdecl
 
   ThreadData^.Func := func;
 
   ThreadData^.Func := func;
 
   ThreadData^.Data := user_data;
 
   ThreadData^.Data := user_data;
   // this creates the Pascal thread
+
   // to tworzy wątek Pascala
 
   BeginThread(@C2P_Translator, ThreadData );
 
   BeginThread(@C2P_Translator, ThreadData );
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
 +
* 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).
  
* Initialize the FPC's threading system by creating a dummy thread. If you don't create any Pascal thread in your app, the thread system won't be initialized (and thus threadvars won't work and thus heap will not work correctly). 
+
<syntaxhighlight lang="pascal">
 
+
type
<syntaxhighlight>type
 
 
   tc = class(tthread)
 
   tc = class(tthread)
 
     procedure execute;override;
 
     procedure execute;override;
Line 560: Line 717:
 
{ main program }  
 
{ main program }  
 
begin
 
begin
   { initialise threading system }
+
   { zainicjować system wątków }
 
   with tc.create(false) do
 
   with tc.create(false) do
 
   begin
 
   begin
Line 566: Line 723:
 
     free;
 
     free;
 
   end;
 
   end;
   { ... your code follows }  
+
   { ... Twój kod wykonywany dalej }  
 
end.</syntaxhighlight>
 
end.</syntaxhighlight>
  
(After the threading system is initialized, the runtime may set the system variable "IsMultiThread" to true which is used by FPC routines to perform locks here and there. You should not set this variable manually.)  
+
(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.)
  
 +
* Jeśli z jakiegoś powodu to nie działa, wypróbuj ten kod w funkcji wątku zewnętrznego:
  
* If for some reason this doesn't work for you, try this code in your external thread function:
+
<syntaxhighlight lang="pascal">
 
+
function ExternalThread(param: Pointer): LongInt; stdcall;
<syntaxhighlight>function ExternalThread(param: Pointer): LongInt; stdcall;
 
 
var
 
var
 
   tm: TThreadManager;
 
   tm: TThreadManager;
Line 580: Line 737:
 
   GetThreadManager(tm);
 
   GetThreadManager(tm);
 
   tm.AllocateThreadVars;
 
   tm.AllocateThreadVars;
   InitThread(1000000); // adjust inital stack size here
+
   InitThread(1000000); // dostosuj tutaj początkowy rozmiar stosu
 
    
 
    
   { do something threaded here ... }
+
   { zrób tutaj coś w wątku ... }
 
      
 
      
 
   Result:=0;
 
   Result:=0;
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
  
 +
=== Identyfikacja wątków zewnętrznych ===
 +
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:
  
=== Identyfikacja zewnętrznych wątków ===
+
1. Zapytaj system operacyjny o identyfikator bieżącego wątku podczas uruchamiania aplikacji
Sometimes you even don't know if you have to deal with external threads (e.g. if some C library makes a callback). This can help to analyse this: 
 
  
1. Ask the OS for the ID of the current thread at your application's start
+
<syntaxhighlight lang="pascal">
 +
GetCurrentThreadID() //windows;
 +
GetThreadID() //Darwin/macOS; 
 +
TThreadID(pthread_self) //Linux;
 +
</syntaxhighlight>
  
<syntaxhighlight>Win32: GetCurrentThreadID();
+
2. Zapytaj ponownie o identyfikator bieżącego wątku wewnątrz funkcji wątku i porównaj to z wynikiem kroku 1.
Darwin: GetThreadID(); 
 
Linux: TThreadID(pthread_self);</syntaxhighlight>
 
  
2. Ask again for the ID of the current thread inside the thread function and compare this by the result of step 1.
+
=== Dodawanie opóźnień czasowych ===
 
 
=== Rezygnacja z opóźnień czasowych ===
 
  
 
ThreadSwitch()
 
ThreadSwitch()
Line 613: Line 771:
 
* [[Main Loop Hooks|Petla gówna uchwytów]]
 
* [[Main Loop Hooks|Petla gówna uchwytów]]
 
* [[Asynchronous Calls|Wywołanie asynchroniczne]]
 
* [[Asynchronous Calls|Wywołanie asynchroniczne]]
 
+
* [http://nickhodges.com/MultiThreadingInDelphi/ToC.html Mutlithreading - The Delphi way]
[[Category:Tutorials|Przykłady]]
 
[[Category:Parallel programming|Programowanie równoległe]]
 
[[Category:Multitasking|Wielozadaniowość]]
 
[[Category:FPC]]
 

Revision as of 13:30, 5 February 2023

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

Kurs tworzenia aplikacji wielowątkowych

Wprowadzenie

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.

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ć.

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.

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.

Jeszcze innym zastosowaniem wielowątkowości może być serwer, który jest w stanie dać odpowiedź wielu klientom, w tym samym czasie.

Czy potrzebujesz wielowątkowości?

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.

Przykład: Czytanie dużego pliku i jego przetwarzanie. Zobacz: examples/multithreading/singlethreadingexample1.lpi.

Wielowątkowość jest potrzebna tylko w przypadku

  • używania uchwytów blokujących, takich jak w komunikacji sieci
  • korzystania z wielu procesorów jednocześnie (SMP)
  • algorytmów i bibliotek, które muszą być wywoływane przez API i jako takie nie mogą być podzielone na mniejsze części.

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.

Moduły potrzebne do tworzenia aplikacji wielowątkowej

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)!

Dlatego twój kod aplikacji Lazarusa powinien wyglądać tak:

program MyMultiThreadedProgram;
{$mode objfpc}{$H+}
uses
{$ifdef unix}
  cthreads,
  cmem, // menedżer pamięci c jest w niektórych systemach znacznie szybszy dla aplikacji wielowątkowych
{$endif}
  Interfaces, // to zawiera widżety LCL
  Forms
  { tu możesz dodać następne moduły },

Jeśli o tym zapomnisz i użyjesz TThread, otrzymasz następujący błąd podczas uruchamiania:

 This binary has no thread support compiled in.
 Recompile the application with a thread-driver in the program uses clause before other units using thread.
Light bulb  Uwaga: 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ć sprytnego łączenia.
Light bulb  Uwaga: 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.

Przykład w czystym FPC

Poniższy kod przedstawia bardzo prosty przykład. Testowany z FPC 3.0.4 na Win7.

Program ThreadTest;
{ przetestuj możliwość aplikacji wielowątkowej }
{
      OUTPUT
wątek 1 uruchomiony
wątek 1 zmienna thri 0 Długość(s)= 1
wątek 1 zmienna thri 1 Długość(s)= 2
wątek 1 zmienna thri 2 Długość(s)= 3
wątek 1 zmienna thri 3 Długość(s)= 4
wątek 1 zmienna thri 4 Długość(s)= 5
wątek 1 zmienna thri 5 Długość(s)= 6
wątek 1 zmienna thri 6 Długość(s)= 7
wątek 1 zmienna thri 7 Długość(s)= 8
wątek 1 zmienna thri 8 Długość(s)= 9
wątek 1 zmienna thri 9 Długość(s)= 10
wątek 1 zmienna thri 10 Długość(s)= 11
wątek 1 zmienna thri 11 Długość(s)= 12
wątek 1 zmienna thri 12 Długość(s)= 13
wątek 1 zmienna thri 13 Długość(s)= 14
wątek 1 zmienna thri 14 Długość(s)= 15
wątek 2 uruchomiony
wątek 3 uruchomiony
wątek 1 zmienna thri 15 Długość(s)= 16
wątek 2 zmienna thri 0 Długość(s)= 1
wątek 3 zmienna thri 0 Długość(s)= 1
wątek 1 zmienna thri 16 Długość(s)= 17
...
...
wątek 5 zmienna thri 997 Długość(s)= 998
wątek 5 zmienna thri 998 Długość(s)= 999
wątek 5 zmienna thri 999 Długość(s)= 1000
wątek 5 zakończony
wątek 10 zmienna thri 828 Długość(s)= 829
wątek 9 zmienna thri 675 Długość(s)= 676
wątek 4 zmienna thri 656 Długość(s)= 657
wątek 10 zmienna thri 829 Długość(s)= 830
wątek 9 zmienna thri 676 Długość(s)= 677
wątek 9 zmienna thri 677 Długość(s)= 678
wątek 10 zmienna thri 830 Długość(s)= 831
wątek 10 zmienna thri 831 Długość(s)= 832
wątek 10 zmienna thri 832 Długość(s)= 833
wątek 10 zmienna thri 833 Długość(s)= 834
wątek 10 zmienna thri 834 Długość(s)= 835
wątek 10 zmienna thri 835 Długość(s)= 836
wątek 10 zmienna thri 836 Długość(s)= 837
wątek 10 zmienna thri 837 Długość(s)= 838
wątek 10 zmienna thri 838 Długość(s)= 839
wątek 10 zmienna thri 839 Długość(s)= 840
wątek 9 zmienna thri 678 Długość(s)= 679
...
...
wątek 4 zmienna thri 994 Długość(s)= 995
wątek 4 zmienna thri 995 Długość(s)= 996
wątek 4 zmienna thri 996 Długość(s)= 997
wątek 4 zmienna thri 997 Długość(s)= 998
wątek 4 zmienna thri 998 Długość(s)= 999
wątek 4 zmienna thri 999 Długość(s)= 1000
wątek 4 zakończony
10
	  
}

uses
  {$ifdef unix}cthreads, {$endif} sysutils;

const
  threadcount = 10;
  stringlen = 1000;

var
   finished : longint;

threadvar
   thri : ptrint;

function f(p : pointer) : ptrint;
var
  s : ansistring;
begin
  Writeln('wątek ',longint(p),' uruchomiony');
  thri:=0;
  while (thri<stringlen) do begin
    s:=s+'1'; { stwórz opóźnienie }
    writeln('wątek ',longint(p),' zmienna thri ',thri,' Długość(s)= ',length(s));
	  inc(thri);
  end;
  Writeln('wątek ',longint(p),' zakończony');
  InterLockedIncrement(finished);
  f:=0;
end;


var
   i : longint;

Begin
   finished:=0;
   for i:=1 to threadcount do
     BeginThread(@f,pointer(i));
   while finished<threadcount do ;
   Writeln(finished);
End.

Klasa TThread

Poniższy przykład można znaleźć w katalogu examples/multithreading/.

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.

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.

Light bulb  Uwaga: Metoda Resume jest przestarzała od FPC 2.4.4. Została zastąpiona przez metodę Start.

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.

W zastąpionej metodzie Execute napisz kod, który będzie uruchamiany w wątku.

Klasa TThread ma jedną ważną właściwość: Terminated : boolean;

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.

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.

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.

Dokładne działanie metody Synchronize zależy od platformy, ale zasadniczo wykonuje ona:

  • wysyła wiadomość do głównej kolejki wiadomości i zasypia
  • 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.
  • po wykonaniu przez główny wątek MyMethod, budzi ona uśpiony wątek i przetwarza następną wiadomość
  • następnie wątek jest kontynuowany.

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.

Przykład:

  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
    inherited Create(CreateSuspended);
    FreeOnTerminate := True;
  end;

  procedure TMyThread.ShowStatus;
  // ta metoda jest wykonywana przez główny wątek i dlatego może uzyskać dostęp do wszystkich elementów GUI.
  begin
    Form1.Caption := fStatusText;
  end;
 
  procedure TMyThread.Execute;
  var
    newStatus : string;
  begin
    fStatusText := 'TMyThread Starting...';
    Synchronize(@Showstatus);
    fStatusText := 'TMyThread Running...';
    while (not Terminated) and ([inne wymagane warunki]) do
      begin
        ...
        [tutaj jest kod głównej pętli wątku]
        ...
        if NewStatus <> fStatusText then
          begin
            fStatusText := newStatus;
            Synchronize(@Showstatus);
          end;
      end;
  end;

W aplikacji:

  var
    MyThread : TMyThread;
  begin
    MyThread := TMyThread.Create(True); // W ten sposób nie uruchamia się automatycznie
    ...
    [Tutaj kod inicjuje wszystko, co jest wymagane, zanim wątki zaczną się wykonywać]
    ...
    MyThread.Start;
  end;

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:

  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
    inherited Create(CreateSuspended);
    FreeOnTerminate := True;
  end;

  procedure TMyThread.ShowStatus;
  // ta metoda jest wykonywana przez główny wątek i dlatego może uzyskać dostęp do wszystkich elementów GUI.
  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 ([inne wymagane warunki]) do
      begin
        ...
        [tutaj jest kod głównej pętli wątku]
        ...
        if NewStatus <> fStatusText then
          begin
            fStatusText := newStatus;
            Synchronize(@Showstatus);
          end;
      end;
  end;

W aplikacji:

  Type
    TForm1 = class(TForm)
      Button1: TButton;
      Label1: TLabel;
      procedure Button1Click(Sender: TObject); 
      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;

    // FreeOnTerminate ma wartość true więc nie powinniśmy pisać:
    // MyThread.Free;
    inherited;
  end;

  procedure TForm1.Button1Click(Sender: TObject);
  begin
   MyThread.Start;
  end;

  procedure TForm1.ShowStatus(Status: string);
  begin
    Label1.Caption := Status;
  end;

Ważne rzeczy, na które trzeba zwrócić uwagę

Kontrola stosu w systemie Windows

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.

Poprawny kod do sprawdzenia tego i innych wyjątków, które mogą wystąpić podczas tworzenia wątków, to:

MyThread := TThread.Create(False);

if Assigned(MyThread.FatalException) then
  raise MyThread.FatalException;

Ten kod zapewni, że każdy wyjątek, który wystąpił podczas tworzenia wątku, zostanie zgłoszony w głównym wątku.

Wielowątkowość w pakietach

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:

uses
  {$IFDEF UNIX}{$IFDEF UseCThreads}
  cthreads,
  cmem, // Menedżer pamięci c jest w niektórych systemach znacznie szybszy w przypadku wielowątkowości
  {$ENDIF}{$ENDIF}

Moduł Heaptrc

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 after module cmem.

uses
  {$IFDEF UNIX}{$IFDEF UseCThreads}
  cthreads,
  cmem, // Menedżer pamięci c jest w niektórych systemach znacznie szybszy w przypadku wielowątkowości
  {$ENDIF}{$ENDIF}
  heaptrc,

Initialization and Finalization

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.

Uwaga: Używanie AfterConstruction, gdy CreateSuspended=false jest niebezpieczne, ponieważ wątek już zaczął działać.

Z drugiej strony, destruktor może służyć do finalizowania zasobów obiektu.

type
  TMyThread = class(TThread)
  private
    fRTLEvent: PRTLEvent;
  public
    procedure Create(SomeData: TSomeObject); override;
    destructor Destroy; override;
  end;

procedure TMyThread.Create(SomeData: TSomeObject; CreateSuspended: boolean);
begin
  // przykład: skonfiguruj zdarzenia, sekcje krytyczne i inne zasoby, takie jak pliki lub połączenia z bazami danych
  RTLEventCreate(fRTLEvent);
  inherited Create(CreateSuspended);
end;

destructor TMyThread.Destroy;
begin
  RTLeventDestroy(fRTLEvent);
  inherited Destroy;
end;

Program bez LCL

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.

Wsparcie dla SMP

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!

Debugowanie aplikacji wielowątkowych w Lazarusie

Debugowanie na Lazarusie wymaga GDB i szybko staje się coraz bardziej funkcjonalne i stabilne. Jednak nadal istnieje kilka dystrybucji Linuksa z pewnymi problemami.

Wyjście debuggera

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.

Moduł LCLProc zawiera kilka funkcji, które pozwalają każdemu wątkowi zapisywać do własnego pliku dziennika:

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

Na przykład: Zamiast writeln('Jakiś tekst', 123); użyj

 DebuglnThreadLog(['Jakiś tekst ',123]);

Spowoduje to dodanie linii 'Jakiś tekst 123' do Log<PID>.txt, gdzie <PID> jest identyfikatorem procesu bieżącego wątku.

Dobrym pomysłem jest usunięcie plików dziennika przed każdym uruchomieniem:

 rm -f Log* && ./project1

Linux

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.

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...

 Projekt -> Opcje projektu -> Opcje kompilatora -> Odpluskwianie, i zaznacz: Use external gdb debug symbols file (-Xg).

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ą:

 X :1 &

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).

Następnie możesz, jeśli chcesz, utworzyć sesję pulpitu na X, za pomocą polecenia:

 gnome-session --display=:1 &

Następnie w Lazarusie w oknie dialogowym parametrów uruchamiania projektu (Uruchom -> Uruchom z parametrami -> Ekran) zaznacz opcję „Użyj ekranu” i wpisz :1.

Teraz aplikacja będzie działać na drugim serwerze X i będziesz mógł ją debugować na pierwszym.

Zostało to przetestowane z Free Pascal 2.0 i Lazarus 0.9.10 w systemie Linux.


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.

Linia poleceń do uruchomienia Xnest to

 Xnest :1 -ac

tworzy sesję X na :1 i wyłącza kontrolę dostępu.

Interfejsy widżetów Lazarusa

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.

Użycie SendMessage/PostMessage do komunikacji pomiędzy wątkami

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.

Zobacz także dokumentację tych procedur:

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.

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.

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:

const
  WM_GOT_ERROR           = LM_USER + 2004;
  WM_VERBOSE             = LM_USER + 2005;

procedure VerboseLog(Msg: string);
var
  PError: PChar;
begin
  if MessageHandler = 0 then Exit;
  PError := StrAlloc(Length(Msg)+1);
  StrCopy(PError, PChar(Msg));
  PostMessage(formConsole.Handle, WM_VERBOSE, Integer(PError), 0);
end;

I przykład, jak obsłużyć tę wiadomość z okna:

const
  WM_GOT_ERROR           = LM_USER + 2004;
  WM_VERBOSE             = LM_USER + 2005;

type
  { TformConsole }

  TformConsole = class(TForm)
    DebugList: TListView;
    // ...
  private
    procedure HandleDebug(var Msg: TLMessage); message WM_VERBOSE;
  end;

var
  formConsole: TformConsole;

implementation

....

{ TformConsole }

procedure TformConsole.HandleDebug(var Msg: TLMessage);
var
  Item: TListItem;
  MsgStr: PChar;
  MsgPasStr: string;
begin
  MsgStr := PChar(Msg.wparam);
  MsgPasStr := StrPas(MsgStr);
  Item := DebugList.Items.Add;
  Item.Caption := TimeToStr(SysUtils.Now);
  Item.SubItems.Add(MsgPasStr);
  Item.MakeVisible(False);

// Po którym następuje coś takiego

  TrayControl.SetError(MsgPasStr);
  StrDispose(MsgStr)
end;

end.

Sekcje krytyczne

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.

Sekcje krytyczne zwykle są używane w ten sposób:

Deklaracja sekcji (globalnie dla wszystkich wątków, które powinny mieć dostęp do tej sekcji):

 MyCriticalSection: TRTLCriticalSection;

Utworzenie sekcji:

 InitializeCriticalSection(MyCriticalSection);

Uruchomienie kilka wątków. Robienie czegoś na wyłączność:

EnterCriticalSection(MyCriticalSection);
try
  // uzyskaj dostęp do zmiennych, zapisz pliki, wyślij pakiety sieciowe itp.
finally
  LeaveCriticalSection(MyCriticalSection);
end;

Po zakończeniu wszystkich wątków zwolnij sekcję krytyczną:

 DeleteCriticalSection(MyCriticalSection);

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.

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).

Na przykład: 5 wątków zwiększających licznik. Zobacz lazarus/examples/multithreading/criticalsectionexample1.lpi

Warning-icon.png

Ostrzeżenie: 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.

Współdzielenie zmiennych

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.

Na przykład: 5 wątków zwiększających licznik. Zobacz lazarus/examples/multithreading/criticalsectionexample1.lpi

Oczekiwanie na inny wątek

Jeśli wątek A potrzebuje wyniku innego wątku B, musi on poczekać, aż B zakończy pracę.

Ważne: Główny wątek nigdy nie powinien czekać na kolejny wątek. Zamiast tego użyj opcji Synchronizuj (patrz wyżej).

Zobacz przykład: lazarus/examples/multithreading/waitforexample1.lpi

{ TThreadA }

procedure TThreadA.Execute;
begin
  Form1.ThreadB:=TThreadB.Create(false);
  // Utwórz zdarzenie
  WaitForB:=RTLEventCreate;
  while not Application.Terminated do begin
    // czekaj w nieskończoność (aż B obudzi 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: Pracuje ...
    Sleep(1500);
    inc(Counter);
    // wybudza A
    RtlEventSetEvent(Form1.ThreadA.WaitForB);
  end;
end;
Light bulb  Uwaga: RtlEventSetEvent można wywołać przed RtlEventWaitFor. Wtedy RtlEventWaitFor natychmiast powróci. Użyj RLeventResetEvent, aby wyczyścić flagę.

Wątek potomny, rozwidlenie (fork)

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.

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ć.

Procedury/pętle równoległe

Szczególnym przypadkiem wielowątkowości jest równoległe uruchamianie pojedynczej procedury. Zobacz Procedury równoległe.

Obliczenia rozproszone

Następnym wyższym krokiem po wielowątkowości jest uruchamianie wątków na wielu komputerach.

  • 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.
  • Możesz użyć bibliotek przekazujących komunikaty, takich jak MPICH, które są używane do HPC (High Performance Computing) w klastrach.

Wątki zewnętrzne

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).

Kwestie do rozważenia podczas korzystania z wątków zewnętrznych (mogą nie być potrzebne we wszystkich lub przyszłych wersjach kompilatora):

  • 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.

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:

type
 TCdeclThreadFunc = function (user_data:Pointer):Pointer;cdecl;

 PCdeclThreadFuncData = ^TCdeclThreadFuncData;
 TCdeclThreadFuncData = record
   Func: TCdeclThreadFunc;  //funkcja cdecl
   Data: Pointer;           //oryginalne dane
 end;

// Wątek Pascala wywołuje funkcję cdecl
function C2P_Translator(FuncData: pointer) : ptrint;
var
  ThreadData: TCdeclThreadFuncData;
begin
  ThreadData := PCdeclThreadFuncData(FuncData)^;
  Result := ptrint(ThreadData.Func(ThreadData.Data));
end;

procedure CreatePascalThread;
var
  ThreadData: PCdeclThreadFuncData;
begin
  New(ThreadData);
  // to jest pożądana funkcja wątku cdecl
  ThreadData^.Func := func;
  ThreadData^.Data := user_data;
  // to tworzy wątek Pascala
  BeginThread(@C2P_Translator, ThreadData );
end;
  • 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).
type
   tc = class(tthread)
     procedure execute;override;
   end;

   procedure tc.execute;
   begin
   end;

{ main program } 
begin
  { zainicjować system wątków }
   with tc.create(false) do
   begin
     waitfor;
     free;
   end;
   { ... Twój kod wykonywany dalej } 
end.

(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.)

  • Jeśli z jakiegoś powodu to nie działa, wypróbuj ten kod w funkcji wątku zewnętrznego:
function ExternalThread(param: Pointer): LongInt; stdcall;
var
  tm: TThreadManager;
begin
  GetThreadManager(tm);
  tm.AllocateThreadVars;
  InitThread(1000000); // dostosuj tutaj początkowy rozmiar stosu
  
  { zrób tutaj coś w wątku ... }
    
  Result:=0;
end;

Identyfikacja wątków zewnętrznych

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:

1. Zapytaj system operacyjny o identyfikator bieżącego wątku podczas uruchamiania aplikacji

GetCurrentThreadID() //windows;
GetThreadID() //Darwin/macOS;  
TThreadID(pthread_self) //Linux;

2. Zapytaj ponownie o identyfikator bieżącego wątku wewnątrz funkcji wątku i porównaj to z wynikiem kroku 1.

Dodawanie opóźnień czasowych

ThreadSwitch()

Light bulb  Uwaga: Nie używaj sztuczki z uśpieniem w Windows Sleep(0) ponieważ nie będzie to działać na wszystkich platformach.

Zobacz także