Конференция "Прочее" » Многопоточность и Delphi
 
  • Loginov Dmitry © (23.04.08 23:32) [0]
    Не так давно я достаточно негативно отзывался о реализации класса TThread в Delphi с завлением, что потоки при завершении иногда виснут. Все-таки тогда правда была не на моей стороне. Просто потоки крутятся в различных DLL-ках, и вполне закономерно, что при выгрузке с помощью FreeLibrary библиотеки, в которой еще крутятся потоки, происходят различные неприятные вещи.

    Основное правило, которого необходимо придерживаться при реализации многопоточности в DLL: библиотека обязана экспортировать функцию очистки данных. Перед выгрузкой библиотеки следует вызывать данную функцию (она сможет завершить все потоки), а уже после вызвать FreeLibrary().

    Потоки невозможно уничтожить в секции finalization библиотеки. Пробовать бесполезно, могут быть лишь такие варианты:
    - винда срубает потоки перед выгрузкой библиотеки. В этом случае возникнет зависание при вызове TThread.Free(), т.к. будет вечно ожидаться завершение работы несуществующего потока. Данный вариант происходит при завершении работы процесса.
    - потоки не срубаются. Выгрузка библиотеки с работающими потоками приводит к Access Violation, процесс может завершиться.
    - если и EXE и DLL скомпилированы с пакетами, то выгрузка библиотеки не приводит к AV, однако и потоки не завершаются.

    Дополнительно: потоки нельзя завершить в finalization DLL-ки, поскольку, выгрузка библиотеки и завершение потока - взаимоисключающие вещи. Поток можно создать в initialization библиотеки, однако Windows его не запустит, пока инициализация библиотеки не завершится (эти вещи также взаимоисключающие).

    Все сказанное ни коим образом не относится к работе EXE - там таких проблем не возникает.

    Описанная проблема раскрывается в хэлпе (и всплывала на тех или иных форумах), но не все с подобными явлениями сталкивались, поэтому для кого-либо данная ветка может оказаться полезной.
  • ага (24.04.08 01:00) [1]

    >
    > Loginov Dmitry ©   (23.04.08 23:32)  

    Какой ужжасс...
  • Tirael (24.04.08 02:15) [2]
    а зачем отдельная ф-ция? перед выгрузкой же автоматически вызывается дллмайн с параметром длл_процесс_детач...
  • KSergey © (24.04.08 07:35) [3]
    Предлагаю в след. раз почитать Рихтера про потоки, процессы и т.д.
    Т.к., с одной стороны, описаны стандартные грабли новичков, с другой - и, думаю, автор уже это понимает - все они исходят из вообще непонимания принципов работы потоков в виндовс, к которым TThread является лишь оберткой. Причем обертка эта такова, что она фактически очень слабо управляет собтвенно виндовыми объектами потоков, которыми, впрочем, "мощнее" управлять и нельзя по определению.
    Однако легкость и податливость в управлении "с наружи" другими, "более простыми" компонентами, действительно сильно сбивает с толку при попытках так же "по простому" манипулировать экземпляром объекта TThread.
  • Loginov Dmitry © (24.04.08 07:56) [4]
    > описаны стандартные грабли новичков


    Думаю, что и не только новичков. В той же степени это касается и профессионалов, еще не успевших по каким-либо причинам прочитать MSDN на все 100%
  • KSergey © (24.04.08 08:54) [5]
    > Loginov Dmitry ©   (24.04.08 07:56) [4]
    > не успевших по каким-либо причинам
    > прочитать MSDN на все 100%

    В данном вопросе и объеме достаточно почитать Рихтера, эта тема у него очень подробно раскрыта. Могу ошибаться, проверять не буду, но пожалуй у него легко найти ответы на все вопросы из серии "че за нафик?" по всем упомянутым граблям.
  • Котик Б (24.04.08 09:26) [6]
    Пользуйтесь напрямую
    CreateThread

    - в чём проблема то :)
    Будете потом всё валить на WinAPI вместо TThread...
  • Сергей М. © (24.04.08 10:01) [7]

    > Loginov Dmitry ©   (23.04.08 23:32)


    > Потоки невозможно уничтожить в секции finalization библиотеки


    Возможно.
    Невозможно другое - дождаться сигнала завершения потока в функции ожидания
    Объясняется это довольно просто - в момент финализации dll-модуля PEB текущего процесса заблокирован и система не может внести туда изменения, касаемые TIB завершившегося потока, а флаг завершения потока система может поднять только после внесения этих изменений.
    Т.е. в этой ситуации получается нечто вроде дедлока.
  • DiamondShark © (24.04.08 15:14) [8]

    > Котик Б   (24.04.08 09:26) [6]
    > Пользуйтесь напрямую CreateThread - в чём проблема то :)

    Попользуйся -- узнаешь.
  • Пробегал2... (24.04.08 15:28) [9]
    ну собственно что и требовалось доказать. Помню ту тему, где с апломбом утверждалось, что потоки в Delphi реализованы криво ;) Смешно. Просто кто-то не умеет их готовить.

    Все остальное было сказано верно - читайте Рихтера, особенно про константы, передаваемые в главную функцию DLL. Никакой такой специальной функции экспортируемой библиотекой не надо, MS уже давно все продумала ;)
  • KSergey © (24.04.08 15:44) [10]
    > DiamondShark ©   (24.04.08 15:14) [8]
    > Попользуйся -- узнаешь.

    Суть не в том, что "узнаешь", а в том, кто виноват. :)
  • Loginov Dmitry © (25.04.08 01:03) [11]
    > Все остальное было сказано верно - читайте Рихтера, особенно
    > про константы, передаваемые в главную функцию DLL. Никакой
    > такой специальной функции экспортируемой библиотекой не
    > надо, MS уже давно все продумала ;)


    Перечитал Рихтера (по части многопоточности). Но все им написанное я уже читал в Windows SDK.
    Т.е. есть допустим некая функция DllMain, в которую передается один их 4-х параметров. Насколько я понимаю, параметры DLL_THREAD_ATTACH и DLL_THREAD_DETACH в данной проблеме врядли чем-либо помогут. А DLL_PROCESS_ATTACH и DLL_PROCESS_DETACH это тоже, что и initialization и finalization в DLL (если я правильно понял). Ну и что же тогда "MS уже давно продумала"?
  • Экс-Оригинал (25.04.08 01:55) [12]
    Что-то  непонятно, вообще о чем разговор.
    Потоки вообще, не привязаны к какой-то конкретной билиотеке. К процессу приявязаны, если так можно выразиться.
  • Пробегал2... (25.04.08 02:33) [13]
    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 его не запустит, пока инициализация библиотеки не завершится (эти вещи также взаимоисключающие).


    я не понимаю, откуда ты черпаешь информацию.
  • Пробегал2... (25.04.08 02:36) [14]
    возможно ты не понимаешь как завершать потоки в DLL?

    Вызывается FreeLibrary из приложения. Соответственно, в контексте того же потока вызывается DLL_PROCESS_DETACH (поговорим пока о нем, а не о DLL_THREAD_...), и судя по всему вызывается finalization модулей библиотеки в Delphi.

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

    В самих потоках естественно должна быть своевременная проверка на Terminated свойство потока, и если оно выставлено - кратчайшим способом финализировать все ресурсы и удалять поток.

    Это - правильная идеология работы.
  • Loginov Dmitry © (25.04.08 07:58) [15]
    > я не понимаю, откуда ты черпаешь информацию.


    1. Столкнулся с проблемой на практике, сделал выводы
    2. Подкрепил правильность выводов прочтением Windows SDK и Рихтера

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


    Но эта идеология не работает! Об этом в [0] как раз и идет разговор ;)
  • Игорь Шевченко © (25.04.08 09:42) [16]

    > это ты с чего взял? Потоки, созданные в DLL - надо завершать
    > в DLL, а потоки, созданные в другом месте - ндадо завершать
    > в соответствующем месте.


    Какая разница, где завершать потоки (потоки Windows, а не Delphiйские TThread) ?
  • Loginov Dmitry © (25.04.08 10:42) [17]
    Примечательно, что уничтожение потока нормально отрабатывает при его создании функцией CreateThread. А AV при выгрузке сыплются именно из-за BeginThread. Эта функция вызывает CreateThread, но передает в нее вместо вашей функции некую функцию ThreadWrapper. Видимо, связано именно с этим...
  • Сергей М. © (25.04.08 10:48) [18]

    > Loginov Dmitry ©   (25.04.08 10:42) [17]


    Проиллюстрируй в коде ..
  • Loginov Dmitry © (25.04.08 11:14) [19]
    > Проиллюстрируй в коде ..


    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); // Ошибка есть
     //CreateThread(nil, 0, @MyThreadFunc, 0, 0, I); // Ошибок нет
    end;

    initialization
     StartMythread;
    finalization
     Term := True;

     // Ожидаем завершения потока
     while Term do
       Sleep(0); // AV валятся здесь!!!

     Sleep(0); // Чисто в отладочных целях
    end.



    DLL и ЕХЕ скомпилированы без пакетов.

    В ЕХЕ две кнопки: с LoadLibrary и FreeLibrary
 
Конференция "Прочее" » Многопоточность и Delphi
Есть новые Нет новых   [134435   +34][b:0][p:0.001]