-
Не так давно я достаточно негативно отзывался о реализации класса TThread в Delphi с завлением, что потоки при завершении иногда виснут. Все-таки тогда правда была не на моей стороне. Просто потоки крутятся в различных DLL-ках, и вполне закономерно, что при выгрузке с помощью FreeLibrary библиотеки, в которой еще крутятся потоки, происходят различные неприятные вещи.
Основное правило, которого необходимо придерживаться при реализации многопоточности в DLL: библиотека обязана экспортировать функцию очистки данных. Перед выгрузкой библиотеки следует вызывать данную функцию (она сможет завершить все потоки), а уже после вызвать FreeLibrary().
Потоки невозможно уничтожить в секции finalization библиотеки. Пробовать бесполезно, могут быть лишь такие варианты: - винда срубает потоки перед выгрузкой библиотеки. В этом случае возникнет зависание при вызове TThread.Free(), т.к. будет вечно ожидаться завершение работы несуществующего потока. Данный вариант происходит при завершении работы процесса. - потоки не срубаются. Выгрузка библиотеки с работающими потоками приводит к Access Violation, процесс может завершиться. - если и EXE и DLL скомпилированы с пакетами, то выгрузка библиотеки не приводит к AV, однако и потоки не завершаются.
Дополнительно: потоки нельзя завершить в finalization DLL-ки, поскольку, выгрузка библиотеки и завершение потока - взаимоисключающие вещи. Поток можно создать в initialization библиотеки, однако Windows его не запустит, пока инициализация библиотеки не завершится (эти вещи также взаимоисключающие).
Все сказанное ни коим образом не относится к работе EXE - там таких проблем не возникает.
Описанная проблема раскрывается в хэлпе (и всплывала на тех или иных форумах), но не все с подобными явлениями сталкивались, поэтому для кого-либо данная ветка может оказаться полезной.
-
> > Loginov Dmitry © (23.04.08 23:32)
Какой ужжасс...
-
а зачем отдельная ф-ция? перед выгрузкой же автоматически вызывается дллмайн с параметром длл_процесс_детач...
-
Предлагаю в след. раз почитать Рихтера про потоки, процессы и т.д. Т.к., с одной стороны, описаны стандартные грабли новичков, с другой - и, думаю, автор уже это понимает - все они исходят из вообще непонимания принципов работы потоков в виндовс, к которым TThread является лишь оберткой. Причем обертка эта такова, что она фактически очень слабо управляет собтвенно виндовыми объектами потоков, которыми, впрочем, "мощнее" управлять и нельзя по определению. Однако легкость и податливость в управлении "с наружи" другими, "более простыми" компонентами, действительно сильно сбивает с толку при попытках так же "по простому" манипулировать экземпляром объекта TThread.
-
> описаны стандартные грабли новичков
Думаю, что и не только новичков. В той же степени это касается и профессионалов, еще не успевших по каким-либо причинам прочитать MSDN на все 100%
-
> Loginov Dmitry © (24.04.08 07:56) [4] > не успевших по каким-либо причинам > прочитать MSDN на все 100%
В данном вопросе и объеме достаточно почитать Рихтера, эта тема у него очень подробно раскрыта. Могу ошибаться, проверять не буду, но пожалуй у него легко найти ответы на все вопросы из серии "че за нафик?" по всем упомянутым граблям.
-
Пользуйтесь напрямую CreateThread - в чём проблема то :) Будете потом всё валить на WinAPI вместо TThread...
-
> Loginov Dmitry © (23.04.08 23:32)
> Потоки невозможно уничтожить в секции finalization библиотеки
Возможно. Невозможно другое - дождаться сигнала завершения потока в функции ожидания Объясняется это довольно просто - в момент финализации dll-модуля PEB текущего процесса заблокирован и система не может внести туда изменения, касаемые TIB завершившегося потока, а флаг завершения потока система может поднять только после внесения этих изменений. Т.е. в этой ситуации получается нечто вроде дедлока.
-
> Котик Б (24.04.08 09:26) [6] > Пользуйтесь напрямую CreateThread - в чём проблема то :)
Попользуйся -- узнаешь.
-
ну собственно что и требовалось доказать. Помню ту тему, где с апломбом утверждалось, что потоки в Delphi реализованы криво ;) Смешно. Просто кто-то не умеет их готовить.
Все остальное было сказано верно - читайте Рихтера, особенно про константы, передаваемые в главную функцию DLL. Никакой такой специальной функции экспортируемой библиотекой не надо, MS уже давно все продумала ;)
-
> DiamondShark © (24.04.08 15:14) [8] > Попользуйся -- узнаешь.
Суть не в том, что "узнаешь", а в том, кто виноват. :)
-
> Все остальное было сказано верно - читайте Рихтера, особенно > про константы, передаваемые в главную функцию DLL. Никакой > такой специальной функции экспортируемой библиотекой не > надо, MS уже давно все продумала ;)
Перечитал Рихтера (по части многопоточности). Но все им написанное я уже читал в Windows SDK. Т.е. есть допустим некая функция DllMain, в которую передается один их 4-х параметров. Насколько я понимаю, параметры DLL_THREAD_ATTACH и DLL_THREAD_DETACH в данной проблеме врядли чем-либо помогут. А DLL_PROCESS_ATTACH и DLL_PROCESS_DETACH это тоже, что и initialization и finalization в DLL (если я правильно понял). Ну и что же тогда "MS уже давно продумала"?
-
Что-то непонятно, вообще о чем разговор. Потоки вообще, не привязаны к какой-то конкретной билиотеке. К процессу приявязаны, если так можно выразиться.
-
Loginov Dmitry © (25.04.08 1:03) [11] Ну и что же тогда "MS уже давно продумала"?
то, что:
Loginov Dmitry © (23.04.08 23:32) потоки нельзя завершить в finalization DLL-ки, поскольку, выгрузка библиотеки и завершение потока - взаимоисключающие вещи
это ты с чего взял? Потоки, созданные в DLL - надо завершать в DLL, а потоки, созданные в другом месте - ндадо завершать в соответствующем месте.
Loginov Dmitry © (23.04.08 23:32) Поток можно создать в initialization библиотеки, однако Windows его не запустит, пока инициализация библиотеки не завершится (эти вещи также взаимоисключающие).
я не понимаю, откуда ты черпаешь информацию.
-
возможно ты не понимаешь как завершать потоки в DLL?
Вызывается FreeLibrary из приложения. Соответственно, в контексте того же потока вызывается DLL_PROCESS_DETACH (поговорим пока о нем, а не о DLL_THREAD_...), и судя по всему вызывается finalization модулей библиотеки в Delphi.
Код обработки должен быть следующий - запущенным потокам делается Terminate, после чего идет WaitFor... функции, ожидающие завершения этих потоков.
В самих потоках естественно должна быть своевременная проверка на Terminated свойство потока, и если оно выставлено - кратчайшим способом финализировать все ресурсы и удалять поток.
Это - правильная идеология работы.
-
> я не понимаю, откуда ты черпаешь информацию.
1. Столкнулся с проблемой на практике, сделал выводы 2. Подкрепил правильность выводов прочтением Windows SDK и Рихтера
> Код обработки должен быть следующий - запущенным потокам > делается Terminate, после чего идет WaitFor... функции, > ожидающие завершения этих потоков. > > В самих потоках естественно должна быть своевременная проверка > на Terminated свойство потока, и если оно выставлено - кратчайшим > способом финализировать все ресурсы и удалять поток. > > Это - правильная идеология работы.
Но эта идеология не работает! Об этом в [0] как раз и идет разговор ;)
-
> это ты с чего взял? Потоки, созданные в DLL - надо завершать > в DLL, а потоки, созданные в другом месте - ндадо завершать > в соответствующем месте.
Какая разница, где завершать потоки (потоки Windows, а не Delphiйские TThread) ?
-
Примечательно, что уничтожение потока нормально отрабатывает при его создании функцией CreateThread. А AV при выгрузке сыплются именно из-за BeginThread. Эта функция вызывает CreateThread, но передает в нее вместо вашей функции некую функцию ThreadWrapper. Видимо, связано именно с этим...
-
> Loginov Dmitry © (25.04.08 10:42) [17]
Проиллюстрируй в коде ..
-
> Проиллюстрируй в коде ..
unit DllUnit;
interface
uses
Windows, Messages, SysUtils, Variants, Classes;
implementation
var
Term: Boolean = False;
function MyThreadFunc(Param: Cardinal): Integer; stdcall;
begin
Result := 0;
while not Term do
Sleep(100);
Term := False;
end;
procedure StartMythread;
var
I: Cardinal;
begin
I := 0;
BeginThread(nil, 0, @MyThreadFunc, 0, 0, I); end;
initialization
StartMythread;
finalization
Term := True;
while Term do
Sleep(0);
Sleep(0); end.
DLL и ЕХЕ скомпилированы без пакетов. В ЕХЕ две кнопки: с LoadLibrary и FreeLibrary
|