Difference between revisions of "Threads/ru"

From Lazarus wiki
(Notes on procedural thread management)
(WaitForThreadTerminate)
 
(6 intermediate revisions by the same user not shown)
Line 5: Line 5:
 
Официальная документация находится здесь: [https://www.freepascal.org/docs-html/prog/progch10.html Programmer's guide chapter 10]. Оно подробно описывает интерфейс процедурных потоков.  
 
Официальная документация находится здесь: [https://www.freepascal.org/docs-html/prog/progch10.html Programmer's guide chapter 10]. Оно подробно описывает интерфейс процедурных потоков.  
  
Объектно-ориентированный интерфейс <code>TThread</code> описан более полно на этой странице: [[Multithreaded_Application_Tutorial/ru|Руководство по многопоточному приложению]]
+
Объектно-ориентированный интерфейс <code>TThread</code> описан более полно на этой странице: [[Multithreaded_Application_Tutorial/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>.
  
There are three steps in a thread's lifespan:
+
Продолжительность жизни потока состоит из трех этапов:  
* Another thread creates it by calling <code>BeginThread</code>.
+
* Другой поток создает его, вызывая <code>BeginThread</code>.  
* The thread completes its work, then exits its top level function; or ends itself explicitly by calling <code>EndThread</code>; or is killed by another thread by calling <code>KillThread</code>.
+
* Поток завершает свою работу, затем выходит из функции верхнего уровня; или завершает себя явным образом, вызывая <code>EndThread</code>; или прибивается другим потоком при вызове <code>KillThread</code>.  
* Another thread detects that the thread has terminated, and calls <code>CloseThread</code> to clean up.
+
* Другой поток обнаруживает, что поток завершен, и вызывает <code>CloseThread</code> для очистки.
  
<code>CloseThread</code> does nothing on Posix systems, but on Windows it releases the thread handle. If a program keeps creating new threads but neglects to close them, this is a resource leak and could lead to system instability in extreme cases. All handles are released when the program terminates, but it's still good practice to explicitly call <code>CloseThread</code> as soon as a thread exits. The return value of <code>CloseThread</code> is always 0 on Posix, but non-zero on Windows if successful.
+
<code>CloseThread</code> ничего не делает в системах Posix, но в Windows освобождает дескриптор потока (handle). Если программа продолжает создавать новые потоки, но не закрывает их, это является утечкой ресурсов и в крайних случаях может привести к нестабильности системы. Все дескрипторы (handle) освобождаются, когда программа завершается, но по-прежнему рекомендуется явно вызывать <code>CloseThread</code>, как только поток завершится. Возвращаемое значение <code>CloseThread</code> всегда равно 0 в Posix и ненулевое в Windows в случае успеха.
  
<code>DoneThread</code> and <code>InitThread</code> in the system unit are primarily for internal use, ignore them.
+
<code>DoneThread</code> и <code>InitThread</code> в системном модуле предназначены в первую очередь для внутреннего использования, игнорируйте их.
  
<code>KillThread</code> should be avoided if at all possible. If a thread happens to be blocking a manual synchronisation mechanism when it is terminated, any other thread waiting for that mechanism may be left waiting forever.
+
<code>KillThread</code> следует избегать, если это вообще возможно. Если поток блокирует механизм ручной синхронизации при завершении, любой другой поток, ожидающий реализации этого механизма, может остаться в ожидании навсегда.
  
<code>SuspendThread</code> and <code>ResumeThread</code> are likewise dangerous, and not even supported on Posix systems, due to similar deadlocking concerns. Co-operative synchronisation is the safe alternative (threads themselves check periodically if they are being asked to shut down).
+
<code>SuspendThread</code> и <code>ResumeThread</code> также опасны и даже не поддерживаются в системах Posix из-за аналогичных проблем с взаимоблокировкой. Кооперативная синхронизация - безопасная альтернатива (сами потоки периодически проверяют, получают ли они запрос на завершение работы).
  
<code>ThreadGetPriority</code> and <code>ThreadSetPriority</code> allow setting a thread's priority between -15 (idle) and +15 (critical), on Windows. On Posix these functions are not implemented yet through the procedural interface.
+
<code>ThreadGetPriority</code> и <code>ThreadSetPriority</code> позволяют устанавливать приоритет потока от -15 (простоя) до +15 (критический) в Windows. В Posix эти функции еще не реализованы через процедурный интерфейс.
  
There is a [https://www.freepascal.org/docs-html/rtl/system/getcpucount.html GetCPUCount] function in the system unit. Use this to estimate at runtime how many parallel threads to create.
+
В модуле system есть функция [https://www.freepascal.org/docs-html/rtl/system/getcpucount.html 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.
+
Синхронизация используется для обеспечения безопасного доступа различных потоков или процессов к общим данным. Наиболее эффективные механизмы синхронизации предоставляются операционной системой; механизмы синхронизации FPC действуют как минимальная платформенно-нейтральная оболочка для механизмов операционной системы.
  
The native thread synchronisation mechanisms in the system unit are: critical sections, RTL events, semaphores, <code>WaitForThreadTerminate</code>. The code for these can be found in '''rtl/<arch>/cthreads.pp''' or '''rtl/<arch>/systhrd.inc'''.
+
Собственные механизмы синхронизации потоков в модуле system это: критические секции, события RTL, семафоры, <code>WaitForThreadTerminate</code>. Их код можно найти в '''rtl/<arch>/cthreads.pp''' или '''rtl/<arch>/systhrd.inc'''.
  
Fully cross-process communication requires a more robust solution, as thread synchronisation mechanisms are insufficient. [https://www.freepascal.org/docs-html/fcl/simpleipc/index.html SimpleIPC] may be appropriate for this.
+
Полностью межпроцессное взаимодействие требует более надежного решения, поскольку механизмов синхронизации потоков недостаточно. Для этого может подойти [https://www.freepascal.org/docs-html/fcl/simpleipc/index.html SimpleIPC].
  
  
=== Critical sections ===
+
=== Критические секции ===
  
 +
Используются функции <code>InitCriticalSection</code>, <code>EnterCriticalSection</code>, <code>LeaveCriticalSection</code> и <code>DoneCriticalSection</code>.
  
The functions used are <code>InitCriticalSection</code>, <code>EnterCriticalSection</code>, <code>LeaveCriticalSection</code>, and <code>DoneCriticalSection</code>.
+
Критические секции - это совместный мьютекс кода, позволяющий только одному потоку одновременно входить в защищенный раздел кода при условии, что каждый поток входит и выходит из раздела чисто. Это безопасный кроссплатформенный способ защиты относительно небольших блоков кода.
  
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.
+
Потоки блокируются из секции только при вызове <code>EnterCriticalSection</code>. Если поток каким-то образом может попасть в раздел без вызова <code>EnterCriticalSection</code>, он не блокируется и раздел остается небезопасным. Потоки попадают в критическую секцию в порядке [https://ru.wikipedia.org/wiki/FIFO FIFO].
  
Threads are only blocked from the section when they call <code>EnterCriticalSection</code>. If a thread is somehow able to get into the section without calling <code>EnterCriticalSection</code>, it is not blocked and the section remains unsafe. Threads are released into the critical section in FIFO order.
+
Мьютекс критической секции имеет счетчик блокировок. Если поток, содержащий мьютекс критической секции, вызывает <code>EnterCriticalSection</code> несколько раз, он должен вызвать <code>LeaveCriticalSection</code> равное количество раз, чтобы освободить мьютекс. Если поток выходит из раздела без вызова <code>LeaveCriticalSection</code>, например, из-за необработанного исключения, мьютекс остается заблокированным, а другие потоки, ожидающие его, также будут заблокированы.
 
 
The critical section mutex has a lock counter. If a thread holding the critical section mutex calls <code>EnterCriticalSection</code> repeatedly, it must call <code>LeaveCriticalSection</code> an equal number of times to release the mutex. If a thread exits the section without calling <code>LeaveCriticalSection</code>, eg. due to an unhandled exception, the mutex remains locked and other threads waiting for it will be deadlocked.
 
 
 
Calling <code>LeaveCriticalSection</code> when the mutex is not locked causes an error on Posix systems, but on Windows it decrements the lock counter to below 0. Calling <code>DoneCriticalSection</code> 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.
 
  
 +
Вызов <code>LeaveCriticalSection</code>, когда мьютекс не заблокирован, вызывает ошибку в системах Posix, но в Windows он уменьшает счетчик блокировок до значения ниже 0. Вызов <code>DoneCriticalSection</code>, пока мьютекс все еще используется, вызывает ошибку в системах Posix, но в Windows он просто блокирует все потоки, ожидающие мьютекса, хотя любой поток, уже находящийся в критической секции, может нормально выйти.
  
 
=== RTLevent ===
 
=== RTLevent ===
  
  
The functions used are <code>RTLEventCreate</code>, <code>RTLEventSetEvent</code>, <code>RTLEventResetEvent</code>, <code>RTLEventWaitFor</code>, and <code>RTLEventDestroy</code>.
+
Функциями используются <code>RTLEventCreate</code>, <code>RTLEventSetEvent</code>, <code>RTLEventResetEvent</code>, <code>RTLEventWaitFor</code>, и <code>RTLEventDestroy</code>.
 
 
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.
+
События RTL являются предпочтительным методом межплатформенной синхронизации. События RTL запукаются как неустановленные. Они блокируют потоки, ожидающие их; когда событие установлено, один ожидающий поток освобождается (в порядке [https://ru.wikipedia.org/wiki/FIFO FIFO]), и событие немедленно сбрасывается.
  
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.
+
Из-за различий в платформах невозможно напрямую запросить текущее состояние события. Также нет способа напрямую определить тайм-аут ожидания события, потому что любое ожидание события вызывает автоматический сброс состояния, а ожидания не имеют возвращаемого значения.
  
 +
Если поток начинает ждать события RTL, которое уже было установлено, ожидание завершается немедленно, и событие автоматически сбрасывается. Событие не учитывает, сколько раз оно было установлено, поэтому несколько наборов по-прежнему очищаются одним сбросом. Будьте осторожны: если у вас есть 8 потоков, которые начинают ждать одного события RTL, которое будет установлено 8 раз, потоки могут зайти в тупик, если что-то очистит несколько наборов за один сброс.
  
=== Semaphores ===
+
Уничтожение события RTL, пока поток ожидает его, не освобождает поток. Ответственность за то, чтобы ни один поток не ожидал события после его уничтожения, лежит на программисте.
  
 +
=== Семафоры ===
  
Semaphores were Posix-only, and have been deprecated. They block threads waiting for them, and each <code>SemaphorePost</code> releases one waiting thread. Although semaphores are not available through the RTL, other implementations exist. (where?)
+
Семафоры существовали только для Posix и устарели. Они блокируют ожидающие их потоки, и каждый <code>SemaphorePost</code> освобождает один ожидающий поток. Хотя семафоры недоступны через RTL, существуют и другие реализации. (где?)
 
 
  
 
=== WaitForThreadTerminate ===
 
=== WaitForThreadTerminate ===
  
 +
<code>WaitForThreadTerminate</code> блокирует текущий поток до тех пор, пока целевой поток не завершится. Параметр тайм-аута игнорируется на платформах Posix, и время ожидания никогда не истечет.
  
<code>WaitForThreadTerminate</code> blocks the current thread until the target thread has exited. The timeout parameter is ignored on Posix platforms, and the wait will never timeout.
+
Известно, что <code>WaitForSingleObject</code> и <code>WaitForMultipleObjects</code> не существуют в Linux, поэтому они не подходят для межплатформенного использования. Они могут быть полезны лишь под Windows.
 
 
<code>WaitForSingleObject</code> and <code>WaitForMultipleObjects</code> famously do not exist under Linux, so they're not good for cross-platform use. They can be of some use under Windows.
 

Latest revision as of 07:14, 21 August 2020

English (en) русский (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. Используйте ее, чтобы оценить во время выполнения, сколько параллельных потоков нужно создать.

Синхронизация потоков

Синхронизация используется для обеспечения безопасного доступа различных потоков или процессов к общим данным. Наиболее эффективные механизмы синхронизации предоставляются операционной системой; механизмы синхронизации FPC действуют как минимальная платформенно-нейтральная оболочка для механизмов операционной системы.

Собственные механизмы синхронизации потоков в модуле system это: критические секции, события RTL, семафоры, WaitForThreadTerminate. Их код можно найти в rtl/<arch>/cthreads.pp или rtl/<arch>/systhrd.inc.

Полностью межпроцессное взаимодействие требует более надежного решения, поскольку механизмов синхронизации потоков недостаточно. Для этого может подойти SimpleIPC.


Критические секции

Используются функции InitCriticalSection, EnterCriticalSection, LeaveCriticalSection и DoneCriticalSection.

Критические секции - это совместный мьютекс кода, позволяющий только одному потоку одновременно входить в защищенный раздел кода при условии, что каждый поток входит и выходит из раздела чисто. Это безопасный кроссплатформенный способ защиты относительно небольших блоков кода.

Потоки блокируются из секции только при вызове EnterCriticalSection. Если поток каким-то образом может попасть в раздел без вызова EnterCriticalSection, он не блокируется и раздел остается небезопасным. Потоки попадают в критическую секцию в порядке FIFO.

Мьютекс критической секции имеет счетчик блокировок. Если поток, содержащий мьютекс критической секции, вызывает EnterCriticalSection несколько раз, он должен вызвать LeaveCriticalSection равное количество раз, чтобы освободить мьютекс. Если поток выходит из раздела без вызова LeaveCriticalSection, например, из-за необработанного исключения, мьютекс остается заблокированным, а другие потоки, ожидающие его, также будут заблокированы.

Вызов LeaveCriticalSection, когда мьютекс не заблокирован, вызывает ошибку в системах Posix, но в Windows он уменьшает счетчик блокировок до значения ниже 0. Вызов DoneCriticalSection, пока мьютекс все еще используется, вызывает ошибку в системах Posix, но в Windows он просто блокирует все потоки, ожидающие мьютекса, хотя любой поток, уже находящийся в критической секции, может нормально выйти.

RTLevent

Функциями используются RTLEventCreate, RTLEventSetEvent, RTLEventResetEvent, RTLEventWaitFor, и RTLEventDestroy.

События RTL являются предпочтительным методом межплатформенной синхронизации. События RTL запукаются как неустановленные. Они блокируют потоки, ожидающие их; когда событие установлено, один ожидающий поток освобождается (в порядке FIFO), и событие немедленно сбрасывается.

Из-за различий в платформах невозможно напрямую запросить текущее состояние события. Также нет способа напрямую определить тайм-аут ожидания события, потому что любое ожидание события вызывает автоматический сброс состояния, а ожидания не имеют возвращаемого значения.

Если поток начинает ждать события RTL, которое уже было установлено, ожидание завершается немедленно, и событие автоматически сбрасывается. Событие не учитывает, сколько раз оно было установлено, поэтому несколько наборов по-прежнему очищаются одним сбросом. Будьте осторожны: если у вас есть 8 потоков, которые начинают ждать одного события RTL, которое будет установлено 8 раз, потоки могут зайти в тупик, если что-то очистит несколько наборов за один сброс.

Уничтожение события RTL, пока поток ожидает его, не освобождает поток. Ответственность за то, чтобы ни один поток не ожидал события после его уничтожения, лежит на программисте.

Семафоры

Семафоры существовали только для Posix и устарели. Они блокируют ожидающие их потоки, и каждый SemaphorePost освобождает один ожидающий поток. Хотя семафоры недоступны через RTL, существуют и другие реализации. (где?)

WaitForThreadTerminate

WaitForThreadTerminate блокирует текущий поток до тех пор, пока целевой поток не завершится. Параметр тайм-аута игнорируется на платформах Posix, и время ожидания никогда не истечет.

Известно, что WaitForSingleObject и WaitForMultipleObjects не существуют в Linux, поэтому они не подходят для межплатформенного использования. Они могут быть полезны лишь под Windows.