Difference between revisions of "Threads/ru"
Line 13: | Line 13: | ||
<code>GetCurrentThreadID</code> возвращает конкретно идентификатор потока (ID), а не дескриптор (handle). Таким образом, хотя вы можете использовать это в системах Posix для вызова функций потоковой передачи RTL, в Windows это не сработает. Чтобы получить дескриптор (handle), вы можете использовать <code>GetCurrentHandle</code> или <code>OpenThread</code> в Windows API или сохранить дескриптор (handle), полученный от <code>BeginThread</code>. | <code>GetCurrentThreadID</code> возвращает конкретно идентификатор потока (ID), а не дескриптор (handle). Таким образом, хотя вы можете использовать это в системах Posix для вызова функций потоковой передачи RTL, в Windows это не сработает. Чтобы получить дескриптор (handle), вы можете использовать <code>GetCurrentHandle</code> или <code>OpenThread</code> в Windows API или сохранить дескриптор (handle), полученный от <code>BeginThread</code>. | ||
− | + | Продолжительность жизни потока состоит из трех этапов: | |
− | * | + | * Другой поток создает его, вызывая <code>BeginThread</code>. |
− | * | + | * Поток завершает свою работу, затем выходит из функции верхнего уровня; или завершает себя явным образом, вызывая <code>EndThread</code>; или прибивается другим потоком при вызове <code>KillThread</code>. |
− | * | + | * Другой поток обнаруживает, что поток завершен, и вызывает <code>CloseThread</code> для очистки. |
− | <code>CloseThread</code> | + | <code>CloseThread</code> ничего не делает в системах Posix, но в Windows освобождает дескриптор потока (handle). Если программа продолжает создавать новые потоки, но не закрывает их, это является утечкой ресурсов и в крайних случаях может привести к нестабильности системы. Все дескрипторы (handle) освобождаются, когда программа завершается, но по-прежнему рекомендуется явно вызывать <code>CloseThread</code>, как только поток завершится. Возвращаемое значение <code>CloseThread</code> всегда равно 0 в Posix и ненулевое в Windows в случае успеха. |
− | <code>DoneThread</code> | + | <code>DoneThread</code> и <code>InitThread</code> в системном модуле предназначены в первую очередь для внутреннего использования, игнорируйте их. |
− | <code>KillThread</code> | + | <code>KillThread</code> следует избегать, если это вообще возможно. Если поток блокирует механизм ручной синхронизации при завершении, любой другой поток, ожидающий реализации этого механизма, может остаться в ожидании навсегда. |
− | <code>SuspendThread</code> | + | <code>SuspendThread</code> и <code>ResumeThread</code> также опасны и даже не поддерживаются в системах Posix из-за аналогичных проблем с взаимоблокировкой. Кооперативная синхронизация - безопасная альтернатива (сами потоки периодически проверяют, получают ли они запрос на завершение работы). |
− | <code>ThreadGetPriority</code> | + | <code>ThreadGetPriority</code> и <code>ThreadSetPriority</code> позволяют устанавливать приоритет потока от -15 (простоя) до +15 (критический) в Windows. В Posix эти функции еще не реализованы через процедурный интерфейс. |
− | + | В модуле system есть функция [https://www.freepascal.org/docs-html/rtl/system/getcpucount.html GetCPUCount]. Используйте ее, чтобы оценить во время выполнения, сколько параллельных потоков нужно создать. | |
== Thread synchronisation == | == Thread synchronisation == |
Revision as of 05:55, 21 August 2020
│
English (en) │
polski (pl) │
русский (ru) │
Free Pascal поддерживает программирование потоков с процедурным и объектно-ориентированным интерфейсом, который в основном не зависит от платформы.
Официальная документация находится здесь: Programmer's guide chapter 10. Оно подробно описывает интерфейс процедурных потоков.
Объектно-ориентированный интерфейс TThread
описан более полно на этой странице: Руководство по разработке многопоточного приложения
Заметки по процедурному управлению потоками
BeginThread
возвращает дескриптор потока (handle) напрямую, а также возвращает идентификатор потока (ID) в качестве выходного параметра. В системах Posix дескрипторы потоков (handle) отсутствуют, поэтому копия идентификатора потока возвращается и как идентификатор (ID), и как дескриптор(handle). В Windows дескриптор потока (handle) используется для управления всеми потоками, а ID - это отдельное уникальное значение, используемое в качестве перечислителя потоков. Следовательно, независимо от платформы, дескриптор потока, возвращаемый BeginThread
, соответствует ожиданиям всех других функций потоковой передачи RTL. (CloseThread
, SuspendThread
, WaitForThreadTerminate
и т.д.)
GetCurrentThreadID
возвращает конкретно идентификатор потока (ID), а не дескриптор (handle). Таким образом, хотя вы можете использовать это в системах Posix для вызова функций потоковой передачи RTL, в Windows это не сработает. Чтобы получить дескриптор (handle), вы можете использовать GetCurrentHandle
или OpenThread
в Windows API или сохранить дескриптор (handle), полученный от BeginThread
.
Продолжительность жизни потока состоит из трех этапов:
- Другой поток создает его, вызывая
BeginThread
. - Поток завершает свою работу, затем выходит из функции верхнего уровня; или завершает себя явным образом, вызывая
EndThread
; или прибивается другим потоком при вызовеKillThread
. - Другой поток обнаруживает, что поток завершен, и вызывает
CloseThread
для очистки.
CloseThread
ничего не делает в системах Posix, но в Windows освобождает дескриптор потока (handle). Если программа продолжает создавать новые потоки, но не закрывает их, это является утечкой ресурсов и в крайних случаях может привести к нестабильности системы. Все дескрипторы (handle) освобождаются, когда программа завершается, но по-прежнему рекомендуется явно вызывать CloseThread
, как только поток завершится. Возвращаемое значение CloseThread
всегда равно 0 в Posix и ненулевое в Windows в случае успеха.
DoneThread
и InitThread
в системном модуле предназначены в первую очередь для внутреннего использования, игнорируйте их.
KillThread
следует избегать, если это вообще возможно. Если поток блокирует механизм ручной синхронизации при завершении, любой другой поток, ожидающий реализации этого механизма, может остаться в ожидании навсегда.
SuspendThread
и ResumeThread
также опасны и даже не поддерживаются в системах Posix из-за аналогичных проблем с взаимоблокировкой. Кооперативная синхронизация - безопасная альтернатива (сами потоки периодически проверяют, получают ли они запрос на завершение работы).
ThreadGetPriority
и ThreadSetPriority
позволяют устанавливать приоритет потока от -15 (простоя) до +15 (критический) в Windows. В Posix эти функции еще не реализованы через процедурный интерфейс.
В модуле system есть функция GetCPUCount. Используйте ее, чтобы оценить во время выполнения, сколько параллельных потоков нужно создать.
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, 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
Semaphores were Posix-only, and have been deprecated. They block threads waiting for them, and each SemaphorePost
releases one waiting thread. Although semaphores are not available through the RTL, other implementations exist. (where?)
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.