Вопрос управления потоками является важной частью разработки многопоточных приложений, особенно в среде Delphi, где работа с потоками имеет свои особенности. В данной статье мы рассмотрим проблему взаимной блокировки, возникающей при ожидании завершения потоков, и предложим решение, позволяющее избежать изменений в дизайне существующей системы.
Проблема взаимной блокировки
Пользователь столкнулся с проблемой взаимной блокировки в многопоточной системе, где потоки добавляют свои идентификаторы в глобальный список TThreadList при запуске и удаляют их по завершении работы. Также потоки проверяют глобальный объект события, который может уведомить их о необходимости отмены работы.
В коде основного потока перед ожиданием завершения потоков происходит разблокировка списка, что может привести к взаимной блокировке, поскольку потоки сами удаляют свои идентификаторы из списка. Без блокировки контекстный переключатель может привести к тому, что поток освободит себя, что вызовет немедленное возвращение функции WaitForMultipleObjects с кодом WAIT_FAILED.
Предложенное решение
Основная ошибка заключается в неправильном использовании метода LockList(). Как только вызов UnlockList() выполнен, список больше не защищен, и потоки могут модифицировать его, что может произойти до или во время вызова WaitForMultipleObjects.
Решение заключается в следующем:
Заблокировать список.
Скопировать идентификаторы потоков в локальный массив.
Разблокировать список.
Ожидать завершения потоков, используя локальный массив идентификаторов.
procedure WaitForWorkers;
var
ThreadHandleList: TList;
ThreadHandleArr: array of THandle;
begin
ThreadHandleList := TWorkerThread.WorkerHandleList.LockList;
try
SetLength(ThreadHandleArr, ThreadHandleList.Count);
for I := 0 to ThreadHandleList.Count-1 do
ThreadHandleArr[i] := ThreadHandleList[I].Handle;
finally
TWorkerThread.WorkerHandleList.UnlockList;
end;
WaitForMultipleObjects(Length(ThreadHandleArr), PWOHandleArray(ThreadHandleArr), True, INFINITE);
end;
Однако даже это решение имеет расовую условие. Некоторые потоки могут уже завершить свою работу и уничтожить свои идентификаторы до того, как WaitForMultipleObjects будет вызван. И оставшиеся потоки могут уничтожить свои идентификаторы в процессе выполнения этой функции. В любом случае, это решение не будет работать. Вы не можете уничтожать идентификаторы потоков, пока активно ожидаете их завершения.
Альтернативные подходы
Отказаться от использования WaitForMultipleObjects и периодически проверять список на пустоту.
Использовать семафор или счетчик, блокированный одновременно, для отслеживания количества активных потоков.
Использовать событие, которое сбрасывается при добавлении первого потока в список и сигнализируется при удалении последнего потока из списка.
Заключение
Важно понимать, что использование FreeOnTerminate=True безопасно только для потоков, которые запускаются и затем забываются. Если вам нужно обращаться к потокам по каким-либо причинам, использование FreeOnTerminate=True может быть очень опасным. Необходимо тщательно планировать стратегию ожидания, чтобы избежать взаимной блокировки и обеспечить корректное завершение работы потоков.
Управление потоками в Delphi требует внимательного подхода, чтобы избежать взаимной блокировки при ожидании завершения потоков, особенно при использовании глобальных структур и событий.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.