Threads/ru
│
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. Используйте ее, чтобы оценить во время выполнения, сколько параллельных потоков нужно создать.
Синхронизация потоков
Синхронизация используется для обеспечения безопасного доступа различных потоков или процессов к общим данным. Наиболее эффективные механизмы синхронизации предоставляются операционной системой; механизмы синхронизации 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.