Threads/pl

From Free Pascal wiki
Revision as of 02:14, 11 December 2021 by Slawek (talk | contribs) (→‎Uwagi dotyczące proceduralnego zarządzania wątkami: usunięcie tekstu en)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigationJump to search

English (en) polski (pl) русский (ru)

Wątki

Free Pascal obsługuje programowanie wątków z interfejsem proceduralnym i obiektowym, który jest w większości neutralny dla platformy.

Oficjalna dokumentacja znajduje się tutaj: Poradnik programisty rozdział 10. Szczegółowo opisuje proceduralny interfejs wątkowości.

Interfejs zorientowany obiektowo TThread jest opisany bardziej obszernej na tej stronie: Kurs tworzenia aplikacji wielowątkowych

Uwagi dotyczące proceduralnego zarządzania wątkami

BeginThread zwraca bezpośrednio uchwyt wątku, ale także zwraca identyfikator wątku jako parametr wyjściowy. W systemach Posix nie ma uchwytów wątków, więc kopia identyfikatora wątku jest zwracana zarówno jako identyfikator, jak i uchwyt. W systemie Windows uchwyt wątku jest używany do zarządzania wszystkimi wątkami, podczas gdy identyfikator jest oddzielną unikatową wartością używaną jako moduł wyliczający wątki. Dlatego, niezależnie od platformy, uchwyt wątku zwracany przez BeginThread jest tym, czego oczekują wszystkie inne funkcje wątków RTL. (CloseThread, SuspendThread, WaitForThreadTerminate itp.)

GetCurrentThreadID zwraca w szczególności identyfikator wątku, a nie uchwyt. Tak więc, chociaż można by użyć tego w systemach Posix do wywoływania funkcji wątków RTL, to nie zadziałałoby to w systemie Windows. Aby uzyskać uchwyt, możesz użyć GetCurrentHandle lub OpenThread w Windows API lub zachować uchwyt, który pochodzi z BeginThread.

Czas życia wątku składa się z trzech etapów:

  • Inny wątek tworzy go, wywołując BeginThread.
  • Wątek kończy swoją pracę, a następnie wychodzi z funkcji najwyższego poziomu; lub kończy się jawnie, wywołując EndThread; lub zostaje zabity przez inny wątek, wywołując KillThread.
  • Inny wątek wykrywa, że ​​wątek został zakończony i wywołuje CloseThread w celu oczyszczenia.

CloseThread nie robi nic w systemach Posix, ale w Windows zwalnia uchwyt wątku. Jeśli program ciągle tworzy nowe wątki, ale zaniedbuje ich zamknięcie, jest to wyciek zasobów i w skrajnych przypadkach może prowadzić do niestabilności systemu. Wszystkie uchwyty są zwalniane po zakończeniu programu, ale nadal dobrą praktyką jest jawne wywołanie CloseThread zaraz po zakończeniu wątku. Wartość zwracana przez CloseThread to zawsze 0 w Posix, ale w Windows jest niezerowa, jeśli się powiedzie.

DoneThread i InitThread w module system są przeznaczone głównie do użytku wewnętrznego, zignoruj ​​je.

KillThread należy unikać, jeśli to w ogóle możliwe. Jeśli wątek blokuje mechanizm ręcznej synchronizacji po jego zakończeniu, każdy inny wątek oczekujący na ten mechanizm może pozostać w oczekiwaniu na zawsze.

SuspendThread i ResumeThread są również niebezpieczne i nie są nawet obsługiwane w systemach Posix z powodu podobnych problemów z zakleszczeniami. Synchronizacja kooperacyjna jest bezpieczną alternatywą (wątki same sprawdzają okresowo, czy są proszone o zamknięcie).

ThreadGetPriority i ThreadSetPriority umożliwiają ustawienie priorytetu wątku pomiędzy -15 (bezczynny) a +15 (krytyczny) w systemie Windows. W Posix funkcje te nie są jeszcze zaimplementowane przez interfejs proceduralny.

W module system znajduje się funkcja GetCPUCount. Użyj jej, aby oszacować w czasie wykonywania, ile równoległych wątków należy utworzyć.

Thread synchronisation

Synchronisation is used to ensure different threads or processes access shared data in a safe manner. The most efficient synchronisation mechanisms are those provided by the operating system; FPC's synchronisation mechanisms act as a minimal platform-neutral wrapper around the operating system mechanisms.

The native thread synchronisation mechanisms in the system unit are: critical sections, RTL events, semaphores (deprecated), WaitForThreadTerminate. The code for these can be found in rtl/<arch>/cthreads.pp or rtl/<arch>/systhrd.inc.

Fully cross-process communication requires a more robust solution, as thread synchronisation mechanisms are insufficient. SimpleIPC may be appropriate for this.


Critical sections

The functions used are InitCriticalSection, EnterCriticalSection, LeaveCriticalSection, and DoneCriticalSection.

Critical sections are a co-operative code mutex, allowing only a single thread at a time into the protected code section, provided each thread enters and exits the section cleanly. This is a safe cross-platform way of protecting relatively small blocks of code.

Threads are only blocked from the section when they call EnterCriticalSection. If a thread is somehow able to get into the section without calling EnterCriticalSection, it is not blocked and the section remains unsafe. Threads are released into the critical section in FIFO order.

The critical section mutex has a lock counter. If a thread holding the critical section mutex calls EnterCriticalSection repeatedly, it must call LeaveCriticalSection an equal number of times to release the mutex. If a thread exits the section without calling LeaveCriticalSection, eg. due to an unhandled exception, the mutex remains locked and other threads waiting for it will be deadlocked.

Calling LeaveCriticalSection when the mutex is not locked causes an error on Posix systems, but on Windows it decrements the lock counter to below 0. Calling DoneCriticalSection while the mutex is still in use causes an error on Posix systems, but on Windows it just deadlocks any threads waiting on the mutex, though any thread already in the critical section is able to leave normally.


RTLevent

The functions used are RTLEventCreate, RTLEventSetEvent, RTLEventResetEvent, RTLEventWaitFor, and RTLEventDestroy.

RTL events are the preferred cross-platform synchronisation method. RTL events start out as unset. They block threads waiting for them; when the event is set, a single waiting thread is released (in FIFO order) and the event is immediately reset.

Due to platform differences, there is no way to directly query an event's current state. There is also no way to directly detect an event wait timeout, because any wait for the event causes an automatic state reset and waits do not have a return value.

If a thread starts to wait for an RTL event that has already been set, the wait completes immediately and the event is automatically reset. The event does not count how many times it has been set, so multiple sets are still cleared by a single reset. Be careful: if you have 8 threads starting to wait for a single RTL event, which will be set 8 times, threads may become deadlocked if anything clears multiple sets in one reset.

Destroying an RTL event while a thread is waiting for it does not release the thread. It is the programmer's responsibility to ensure no thread is waiting for the event when the event is destroyed.


Semaphores

A semaphore blocks threads waiting for it, and each SemaphorePost releases one waiting thread.

The semaphore implementation in FPC's RTL was Posix-only, and has been deprecated, and removed as of FPC 3.2.0. If semaphores are to be reimplemented in the RTL, it must be compatible with Delphi's SyncObjs.TSemaphore. Outside of the RTL, other custom implementations do exist, for example here.


WaitForThreadTerminate

WaitForThreadTerminate blocks the current thread until the target thread has exited. The timeout parameter is ignored on Posix platforms, and the wait will never timeout.

WaitForSingleObject and WaitForMultipleObjects famously do not exist under Linux, so they're not good for cross-platform use. They can be of some use under Windows.