Конференция "Прочее" » Многопоточность и 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
  • Сергей М. © (25.04.08 11:25) [20]

    > // Ошибок нет


    "Ты суслика видишь ? И я не вижу. А он есть !"  (с)


    > // Ожидаем завершения потока


    Это вовсе не ожидание завершения потока, а всего лишь ожидание момента когда переменная Term примет значение False.


    > Sleep(0); // AV валятся здесь


    Именно здесь AV валиться не может, не выдумывай.
    Неоткуда тут AV взяться.
  • guav © (25.04.08 11:28) [21]
    > BeginThread(nil, 0, @MyThreadFunc, 0, 0, I); // Ошибка есть

    Ошибка в твоём коде.
  • Loginov Dmitry © (25.04.08 11:29) [22]

    > Именно здесь AV валиться не может, не выдумывай.
    > Неоткуда тут AV взяться.


    Я ведь когда-то тоже так думал! А оказывается, что есть откуда.
    И именно на Sleep(0)...
  • Loginov Dmitry © (25.04.08 11:29) [23]

    > Ошибка в твоём коде.


    Где именно?
  • Сергей М. © (25.04.08 11:30) [24]

    > BeginThread(nil, 0, @MyThreadFunc, 0, 0, I); // Ошибка есть


    При объявлении MyThreadFunc ты указал соглашение stdcall  - вот граблями и получил)
  • guav © (25.04.08 11:31) [25]
    Смотри что передаёшь в BeginThread и что она требует.
  • guav © (25.04.08 11:31) [26]
    Кстати, если писать такое без @, то компилятор будет проверять прототип.
  • Сергей М. © (25.04.08 11:32) [27]

    > именно на Sleep(0)


    То, что остановившись на этой строчке по брейкпойнту ты жмакнул F7 и получил AV, вовсе не означает, что именно эта строчка является причиной исключения.
  • Loginov Dmitry © (25.04.08 11:42) [28]
    Мда... Неудачный получился пример. Сорри за невнимательность :(
    Но один фик с TThread AV гарантированно лезут...
  • Сергей М. © (25.04.08 11:44) [29]

    > один фик с TThread AV гарантированно лезут


    Иллюстрируй в коде теперь с TThread..
  • Loginov Dmitry © (25.04.08 11:55) [30]

    > Иллюстрируй в коде теперь с TThread..



    unit DllUnit;

    interface

    uses
     Windows, Messages, SysUtils, Variants, Classes;

    type
     TMyThread = class(TThread)
     protected
       procedure Execute; override;
     end;  

    implementation

    var
     MyThread: TMyThread;

    procedure StartMythread;
    begin
     MyThread := TMyThread.Create(False);
    end;

    { TMyThread }

    procedure TMyThread.Execute;
    begin
     inherited;
     //FreeOnTerminate := True; // Не влияет
     while not Terminated do
       Sleep(50);
    end;

    initialization
     StartMythread;
    finalization
     // сценарий 1
     //MyThread.Terminate; // возникает AV

     // сценарий 2
     //MyThread.Free; // Программа зависает

     // сценарий 3
     //MyThread.Terminate; // Программа
     //MyThread.WaitFor;   // зависает

     // сценарий 4
     // Ничего не делаем - возникает AV
    end.



    Опять ведь найдете че-нибудь.... :)
  • Сергей М. © (25.04.08 12:00) [31]

    > Loginov Dmitry ©   (25.04.08 11:55) [30]


    1,4 - ничего удивительного, абсолютно ожидаемый результат.
    2,3 - см. [7] на предмет причины зависания
  • Loginov Dmitry © (25.04.08 12:13) [32]

    > 1,4 - ничего удивительного, абсолютно ожидаемый результат.
    >
    > 2,3 - см. [7] на предмет причины зависания


    Да насчет зависаний как раз-то все и понятно. Windows SDK об этом говорит вполне четко.

    Таким образом получается, что в любом случае мы ловим или AV, или зависание.
    А можно ли это как-то обойти без дополнительной экспортируемой функции? И как?
  • Сергей М. © (25.04.08 12:39) [33]

    > Loginov Dmitry ©   (25.04.08 12:13) [32]
    >
    >


    Обойти в принципе можно, но реализация обхода будет уже из разряда трюков.
    А трюки, как известно, штука рискованая и ненадежная)
  • Loginov Dmitry © (25.04.08 12:52) [34]
    кстати, в finalization такой трюк обычно спасает:


     MyThread.Terminate;
     while Assigned(MyThread) do
       Sleep(20);



    Но не всегда. В случае, когда FreeLibrary библиотеке не делают, при завершении работы процесса все-равно вызывается для всех DLL finalization, но к этому моменту винда уже успевает срубить все потоки, поэтому данный код приводит лишь к зависанию.
  • Сергей М. © (25.04.08 12:58) [35]

    > такой трюк обычно спасает


    С какого перепугу ?
  • Пробегал2... (25.04.08 13:40) [36]
    Игорь Шевченко ©   (25.04.08 9:42) [16]
    Какая разница, где завершать потоки (потоки Windows, а не Delphiйские TThread) ?


    а зачем в дельфи лишать себя такого удобного класса, как TThread?
  • Loginov Dmitry © (25.04.08 13:40) [37]

    > С какого перепугу ?


    В конце TMyThread.Execute (или в деструкторе) выполняется присвоение MyThread := nil
  • Игорь Шевченко © (25.04.08 14:01) [38]
    Пробегал2...   (25.04.08 13:40) [36]

    Тут в ветке где-то про чтение Рихтера говорили. Нафига читать Рихтера, если он в Delphi ни ухом ни рылом ?
  • Сергей М. © (25.04.08 15:16) [39]

    > Loginov Dmitry ©   (25.04.08 13:40) [37]


    > В конце TMyThread.Execute (или в деструкторе) выполняется
    > присвоение MyThread := nil
    >


    И что ?

    Появление какой-то там nil в какой-то там переменной не есть факт завершения поточной функции.

    Это те же фаберже, чт о и в [19], только вид сбоку)

    Ты, видимо, совершенно не понимаешь, что является главным признаком фактического завершения исполнения поточной функции.
  • Пробегал2... (25.04.08 15:26) [40]
    Loginov Dmitry ©   (25.04.08 13:40) [37]
    В конце TMyThread.Execute (или в деструкторе) выполняется присвоение MyThread := nil


    о боже. Ты вообще понимаешь что делаешь? Блин, и кто-то говорил о профессионализме... В методах классах обращаться к конкретному экземпляру класса?

    Игорь Шевченко ©   (25.04.08 14:01) [38]
    Тут в ветке где-то про чтение Рихтера говорили. Нафига читать Рихтера, если он в Delphi ни ухом ни рылом ?


    я пожалуй даже отвечать не буду.
  • Loginov Dmitry © (25.04.08 15:29) [41]

    > что является главным признаком фактического завершения исполнения
    > поточной функции.


    Возможно возможно...

    Что же по Вашему является "главным признаком фактического завершения исполнения поточной функции", и как это "что" использовать в данной ситуации?
  • Loginov Dmitry © (25.04.08 15:30) [42]

    > о боже. Ты вообще понимаешь что делаешь? Блин, и кто-то
    > говорил о профессионализме... В методах классах обращаться
    > к конкретному экземпляру класса?


    Вот и объясни мне, как здесь можно выкрутиться без подобных извращений!
  • Сергей М. © (25.04.08 15:45) [43]

    > Loginov Dmitry ©   (25.04.08 15:29) [41]


    Признаком фактического завершения поточной функции является либо вызов ф-ции ExitThread() либо вызов машинструкции RET верхнего уровня вложенности.
  • Пробегал2... (25.04.08 15:59) [44]
    Loginov Dmitry ©   (25.04.08 15:30) [42]
    Вот и объясни мне, как здесь можно выкрутиться без подобных извращений!


    тебе что нужно? Завершить поток? Так сделай ему Terminate, поток должен своевременно проверять Terminated свойство и выходить из Execute когда оно true

    Если ты сделаешь FreeOnTerminate := true - это значит, что после выхода из Execute поток сам вызовет Destroy, то есть такому потоку-классу НЕ НУЖНО делать Free / Destroy.

    Если тебе нужно дождаться завершения работы потока - смотри функции WaitForSingleObject / WaitForMultipleObjects, или уже реализованный метод TThread.WaitFor

    Поэтому если смотреть твой пример [30], то при классе со свойством: FreeOnTerminate := true - правильный сценарий 3.

    При FreeOnTerminate := false правилен такой сценарий:

    MyThread.Terminate;
    MyThread.WaitFor;
    MyThread.Free ;



    Косяк может быть действительно в том, когда вызывается код из секции finalization. Я в библиотеках им не пользуюсь. Используй стандартные посылки Windows. То есть:

    library DllEntry;

    procedure DLLEntryPoint(Reason: DWORD);
    begin
     case Reason:
       Dll_Process_Attach: ДЕЛАЕМ ИНИЦИАЛИЗАЦИЮ БИБЛИОТЕКИ
       DLL_PROCESS_DETACH: ДЕЛАЕМ ФИНАЛИЗАЦИЮ ВСЕХ ДАННЫХ БИБЛИОТЕКИ
     end;
    end;

    // Здесь реализация экспортируемых функций

    // экспорт

    begin
     if DllProc = nil then
     begin
       DllProc := @DLLEntryPoint;
       DllEntryPoint(Dll_Process_Attach);
     end;
    end.

  • Сергей М. © (25.04.08 16:23) [45]

    > Пробегал2


    > Косяк может быть действительно в том, когда вызывается код
    > из секции finalization. Я в библиотеках им не пользуюсь


    Пользоваться или не пользоваться - это твое право.
    Но косяк с дедлоком будет в любом случае.
  • Сергей М. © (25.04.08 16:25) [46]

    > Пробегал2...   (25.04.08 15:59) [44]


    > если смотреть твой пример [30], то при классе со свойством:
    >  FreeOnTerminate := true - правильный сценарий 3


    Неправильный он, как и все остальные сценарии.
  • Пробегал2... (25.04.08 16:33) [47]
    Сергей М. ©   (25.04.08 16:23) [45]
    Но косяк с дедлоком будет в любом случае


    где ты там видишь дедлок?! Словами можешь описать?
  • Loginov Dmitry © (25.04.08 16:35) [48]

    > Признаком фактического завершения поточной функции является
    > либо вызов ф-ции ExitThread() либо вызов машинструкции RET
    > верхнего уровня вложенности.


    Вызов <> признаку!


    > тебе что нужно? Завершить поток? Так сделай ему Terminate,
    >  поток должен своевременно проверять Terminated свойство
    > и выходить из Execute когда оно true
    >
    > Если ты сделаешь FreeOnTerminate := true - это значит, что
    > после выхода из Execute поток сам вызовет Destroy, то есть
    > такому потоку-классу НЕ НУЖНО делать Free / Destroy.
    >
    > Если тебе нужно дождаться завершения работы потока - смотри
    > функции WaitForSingleObject / WaitForMultipleObjects, или
    > уже реализованный метод TThread.WaitFor
    >
    > Поэтому если смотреть твой пример [30], то при классе со
    > свойством: FreeOnTerminate := true - правильный сценарий
    > 3.


    фигня какая-то получается. Я с [0] поста доказываю, что в finalization DLL-библиотеки все это не работает, а вы все-равно внушаете, что так правильно. Да, так правильно! Но не работает!

    Все-таки, я все больше прихожу к выводу, что нужна дополнительная экспортируемая функция. С вариантом, когда Винда при выгрузке DLL срубает потоки еще до finalization, достаточно сложно что-либо другое придумать.
    Ведь такое обращения Windows с потоками - опасно и чревато потерей данных...
  • jack128_ (25.04.08 16:41) [49]

    > Словами можешь описать?

    На меня снижа шло озарение и я понял в чем главная проблема нашего мира.  Мы не слышам друг друга...  

    PS
    [7]
  • Сергей М. © (25.04.08 16:42) [50]

    > Пробегал2...   (25.04.08 16:33) [47]


    см. [7], там как раз объяснение "словами".
  • Сергей М. © (25.04.08 16:53) [51]

    > Loginov Dmitry ©   (25.04.08 16:35) [48]


    > Вызов <> признаку!


    Поправлюсь - не сам вызов, а результат его исполнения.

    В результате исполнения вызова ExitThread или возврата по RET управление будет передано в kernel32 и никогда более не будет передано коду поточной функции, что дает все основания для беспроблемной деаллокации участка ВАП, в котором размещался машкод поточной ф-ции.
  • Сергей М. © (25.04.08 17:04) [52]
    В дополнение к [51] - подразумевается, что код поточной ф-ции м.б. смело удален из ВАП процесса не ранее чем завершат свое выполнение все потоки, созданные ранее с указанием этой поточной функции.
  • Пробегал2... (25.04.08 17:14) [53]
    Loginov Dmitry ©   (25.04.08 16:35) [48]
    Я с [0] поста доказываю, что в finalization DLL-библиотеки все это не работает, а вы все-равно внушаете, что так правильно


    как выяснили, в finalization это действительно не работает. Поэтому не надо этот код писать в finalization. ГДЕ НУЖНО ПИСАТЬ этот код - я написал в посте [44]

    jack128_   (25.04.08 16:41) [49]
    На меня снижа шло озарение и я понял в чем главная проблема нашего мира.  Мы не слышам друг друга...  

    PS
    [7]


    теперь понял! Честное слово, почему-то пропустил пост [7], не читал его... Да, наверное так и есть.
  • Сергей М. © (25.04.08 17:20) [54]

    > Пробегал2...   (25.04.08 17:14) [53]


    > не надо этот код писать в finalization. ГДЕ НУЖНО ПИСАТЬ
    > этот код - я написал в посте [44]


    Финализация юнитов вызвается в контексте вызванной DllMain(DLL_PROCESS_DETACH), так что без разницы, где будет вызвана wait-функция ожидания завершения потока - хоть непосредственно в DllMain, хоть в finalization. Результат будет один и тот же - "не дождетесь !")
  • Loginov Dmitry © (25.04.08 17:29) [55]

    > Финализация юнитов вызвается в контексте вызванной DllMain(DLL_PROCESS_DETACH),
    >  так что без разницы, где будет вызвана wait-функция ожидания
    > завершения потока - хоть непосредственно в DllMain, хоть
    > в finalization. Результат будет один и тот же - "не дождетесь
    > !")


    И это правда!
    Проверено! :)
  • Пробегал2... (25.04.08 17:52) [56]
    Сергей М. ©   (25.04.08 17:20) [54]

    странно... даже как-то бредово... А где же тогда рекомендуется завершать потоки, созданные в DLL? Ведь идеологически при DLL_PROCESS_DETACH самое место...

    Ну тогда я вижу один извращенный вариант, других по-моему просто нет.

    TMyThread = class(TThread)
    private
     FFinallyEvent: Thandle ;
    protected
     procedure execute; override;
     constructor Create;
     destructor Destroy;
     property FinallyEvent: Thandle read FFinallyEvent;
    end;

    ....

    constructor TMyThread.Create;
    begin
     FFinallyEvent := CreateEvent(bla bla не помню что там надо) ;
     FreeOnTerminate := true ;
     ResetEvent(FFinallyEvent);  
     inherited Create(false) ;  
    end;

    destructor TMyThread.Destroy;
    begin
     CloseHandle(FFinallyEvent);
     inherited;
    end;

    procedure TMyThread.Execute;
    begin
     while not terminate
       РАБОТАЕМ
     SetEvent(FFinallyEvent) ;
    end;



    Соответственно, вызовы приблизительно такие:

    var
     MyThread: TMyThread;

    ...

    initialization
     MyThread := TMyThread.Create;
    finalization
     MyThread.Terminate;
     WaitForSingleObject(MyThread.FinallyEvent) ;

    end.



    впрочем, как я понимаю, код с FFinallyEvent можно и убрать. Все равно событие выставляется ДО того, как фактически поток будет завершен, просто максимально приближает ожидания момента к реальному моменту уничтожения потока...

    Так что можно и без event'а... Все равно судя по всему FreeLibary вернет управление, но поток будет продолжать крутиться и ВАП не будет очищено, то есть вс в порядке, в результате все равно все финализируется и беспокоится нечего.

    event можно использовать только как знак, что поток уже на финальной стадии завершения, то есть он уже не будет ничего никуда передавать, не будет ничего создавать и открывать и прочее...
  • Loginov Dmitry © (25.04.08 18:06) [57]

    > Пробегал2...   (25.04.08 17:52) [56]


    Практически тот же изврат, что и при while Assigned(MyThread) do...
    но более правильный.
    От срубания потоков виндой все-равно не спасет! :)
  • Пробегал2... (25.04.08 18:12) [58]
    Loginov Dmitry ©   (25.04.08 18:06) [57]
    Практически тот же изврат, что и при while Assigned(MyThread) do...
    но более правильный


    ну нифига... У тебя все расчитано, что максимум будет создан один поток.. Второй поток уже внесет несусветную суматоху. У меня же поток по всем правилам ООП написан ;) Поменяются тольуо функции ожидания для нескольких потоков, будет использована WaitForMultipleObjects

    Loginov Dmitry ©   (25.04.08 18:06) [57]
    От срубания потоков виндой все-равно не спасет! :)


    да с чего ты взял, что винда потоки срубает?!?! Винда финализирует потоки не при FreeLibrary, а при завершении процесса, тогда она все ресурсы пытается освободить, созданные этим процессом.
  • Игорь Шевченко © (25.04.08 19:05) [59]

    > странно... даже как-то бредово... А где же тогда рекомендуется
    > завершать потоки, созданные в DLL? Ведь идеологически при
    > DLL_PROCESS_DETACH самое место...


    потоки не создаются в DLL. Потоки создаются в процессе. Познай эту истину и будет тебе рулез вечный и немеряный
  • Пробегал2... (25.04.08 19:18) [60]
    Игорь Шевченко ©   (25.04.08 19:05) [59]
    потоки не создаются в DLL. Потоки создаются в процессе


    а вам доставляет удовольствие цепляться к словам? Любопытно посто.

    Если действительно непонятна фраза "потоки созданные в DLL" - я уточню. Это потоки, которые созданы в том месте маш. кода, который был загружен в ВАП процесса при проецировании образа DLL.

    К чему еще изволите придраться?
  • Игорь Шевченко © (25.04.08 19:23) [61]
    Пробегал2...   (25.04.08 19:18) [60]

    И чем они отличаются от потоков, созданных в другом месте кода ?
  • Сергей М, (25.04.08 19:30) [62]

    > Пробегал2...   (25.04.08 19:18) [60]


    > Это потоки, которые созданы в том месте маш. кода, который
    > был загружен в ВАП процесса при проецировании образа DLL


    Не нало месить кислое и фиолетовое.
  • Пробегал2... (25.04.08 20:35) [63]
    Игорь Шевченко ©   (25.04.08 19:23) [61]
    И чем они отличаются от потоков, созданных в другом месте кода ?


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

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

    С таким же успехом и глобальные переменные, выделенные в DLL - можно очищать в самом приложении, не так ли? Только это нелогично, это - неудобно.

    Плюс весь WinApi спроектирован таким образом и я думаю стоит придерживаться этого подхода. Когда вам требуется принять некую структуру данных - ВЫ выделяете память под нее и передаете на нее ссылку, по которой эта структура заполняется библиотекой.
    Заметьте, не библиотека выделяет память и передает ссылку на заполненную структуру, которую ВЫ потом должны уничтожить.
    Принцип один: если намусорил - подчисти за СОБОЙ.

    И если там какая-то DLL создала поток для реализации функционала - она же этот поток и должна уничтожить. Имхо, если DLL хорошо спроектирована - то я в любое время могу вызывать FreeLibrary - и DLL должна очистить все ресурсы, занятые ею в процессе работы и вернуть все в то состояние, которое было до вызова LoadLibrary.
  • Игорь Шевченко © (25.04.08 20:42) [64]
    Пробегал2...   (25.04.08 20:35) [63]

    Это всего лишь твое имхо. Ни больше, ни меньше.
    В системе и в приложениях есть примеры и одного подхода, и другого, не надо выдавать свое имхо за абсолютную истину, а главное, делать из этого имха какие-то глобальные выводы.
  • Сергей М, (25.04.08 20:43) [65]
    Удалено модератором
  • Сергей М, (25.04.08 21:00) [66]
    Здесь

    http://msdn2.microsoft.com/en-us/library/ms885202.aspx

    черным по белому нарисована логика вызовов DllMain с тем или иным резоном.

    С учетом же того, что вызовы DllMain конкретного модуля в контексте текущего процесса осуществляются системой синхронно, "феномен зависания" становится совершенно очевидным и понятным, даже без вникания во всякие там PEBы и TIBы.
  • Сергей М, (25.04.08 21:01) [67]
    ))
  • Loginov Dmitry © (25.04.08 21:12) [68]
    > ну нифига... У тебя все расчитано, что максимум будет создан
    > один поток.. Второй поток уже внесет несусветную суматоху.


    мне просто не требуется, чтобы в программе крутилось несколько однотипных потоков, поэтому выбрано такое решение. В других случаях будет выбрано другое решение. Кстати, после WaitForSingleObject() стоило бы выждать хотябы немного времени (например 50 / 100 мс), чтобы стать более уверенным, что функция потока уперлась в ExitThread() (а этот код уже выполняется не в нашей DLL-ке, а в kernel) и ожидает снятия блокировки, в этом случае выгрузка библиотеки уже точно ни на что не повлияет, и никаких AV не будет.

    > да с чего ты взял, что винда потоки срубает?!?! Винда финализирует
    > потоки не при FreeLibrary


    Вот срубает и все тут. Причем FreeLibrary() перед закрытием программы вызывается. В одном случае срубает, в другом - не срубает. Есть конечно подозрение, что где-то в системе лишний раз LoadLibrary() для моей библиотеки кто-то вызвал, но наврядли... Проверю на следующей неделе.


    > И чем они отличаются от потоков, созданных в другом месте
    > кода ?

    Потоковой функцией, код которой выгружается вместе с DLL-кой в том случае, если он принадлежит DLL-ке.
  • Пробегал2... (25.04.08 21:42) [69]
    Игорь Шевченко ©   (25.04.08 20:42) [64]
    Это всего лишь твое имхо. Ни больше, ни меньше


    ок, давайте по другому. Игорь, только честно - у вас были проекты, где поток создавался в коде DLL, а завершался в другом месте?

    P.S. Это я уже не говорю об использовании класса TThread, там уж как ни крути финализировать надо там же, где создаешь.
  • Игорь Шевченко © (25.04.08 22:50) [70]
    Пробегал2...   (25.04.08 21:42) [69]

    Я не единственный разработчик программного обеспечения на этом свете.
    И на слабо бери кого-нибудь другого.
  • Пробегал2... (25.04.08 23:40) [71]
    Игорь Шевченко ©   (25.04.08 22:50) [70]

    как я понимаю, ответ нет, ни в одном проекте вы так не делали.
    Что и требовалось доказать.
  • Игорь Шевченко © (26.04.08 00:13) [72]
    Пробегал2...   (25.04.08 23:40) [71]

    И на слабо бери кого-нибудь другого. Доказатель фигов
  • Пробегал2... (26.04.08 02:52) [73]
    ну хорошо. Я моих проектах никогда не было, чтобы поток создавался в DLL, а завершался в другой DLL или в самом приложении.
    И мне такой подход кажется нелогичным и путанным, когда DLL обязывает выполнять какие-то телодвижения кроме загрузки и выгрузки библиотеки.

    Да, это мое ИМХО. ну не согласны - и не согласны ;)
  • Игорь Шевченко © (26.04.08 11:02) [74]
    Пробегал2...   (26.04.08 02:52) [73]

    С точки зрения процесса, и основное приложения и DLL - это всего лишь части кода, связанные в единое адресное пространство (про различные менеджеры памяти или VMT для дельфийских классов в этом аксепте упоминать сейчас не будем, так как консепция потоков не зависит от используемого языка программирования). Таким образом, ничто не мешает потоку, начавшись в одной части кода. завершится абсолютно в другой.
    Система это делать позволяет.
    Более того, система даже позволяет создать поток в одном приложении, а завершать его в другом, используя функции CreateRemoteThread и TerminateThread

    Давайте все-таки различать - возможно/невозможно и удобно/неудобно
  • Котик Б (26.04.08 16:48) [75]
    Игорь, я Вами восхищаюсь :)
  • имя (26.04.08 20:28) [76]
    Удалено модератором
  • Экс-Оригинал (26.04.08 21:59) [77]
    Кто-нибудь может привести пример(свой, чужой - неважно),
    где бы в DLL создавался поток (BeginThread/TThread), затем DLL из приложения выгружалась бы, а поток продолжал работать?
  • Loginov Dmitry © (26.04.08 22:08) [78]
    > где бы в DLL создавался поток (BeginThread/TThread), затем
    > DLL из приложения выгружалась бы, а поток продолжал работать?


    На ум приходит только одно: потоковая функция расположена в другом модуле.
  • guav © (26.04.08 22:22) [79]

    > > где бы в DLL создавался поток (BeginThread/TThread), затем
    > > DLL из приложения выгружалась бы, а поток продолжал работать?
    > На ум приходит только одно: потоковая функция расположена
    > в другом модуле.


    Потоковая это только адрес начала потока, далее может быть переходы куда угодно. Легко представить себе начало потока в dll, затем продолжение в другой и вызов ExitThread уже оттуда. Можно даже и без ExitThread, обычным возвратом, просто переход в другой модуль не по call, а по jmp.
    Или так: в потоке создаются фиберы, и поток завершается в фибере созданном в другом модуле.
  • Экс-Оригинал (26.04.08 22:53) [80]

    > На ум приходит только одно: потоковая функция расположена
    > в другом модуле.


    т.е.?
  • Пробегал2... (27.04.08 12:32) [81]
    Игорь Шевченко ©   (26.04.08 11:02) [74]
    Давайте все-таки различать - возможно/невозможно и удобно/неудобно


    а процитируйте хоть одно мое слово, где я говорю, что это невозможно?

    На протяжении всей ветки я говорил, что это НЕПРАВИЛЬНО, а не невозможно. С точки зрения логики путанно. Поэтому в моих проектах и я уверен в ваших такого нету.

    И не понимаю почему вы начинаете спорить, а потом зачем-то говорите про различие слов невозможно и неудобно, очевидно с самого начала говорили про неудобно.
  • Игорь Шевченко © (27.04.08 12:35) [82]
    Пробегал2...   (27.04.08 12:32) [81]


    > На протяжении всей ветки я говорил, что это НЕПРАВИЛЬНО,
    >  а не невозможно


    Что есть НЕПРАВИЛЬНО ? Ты, прежде чем претендовать на абсолютную истину, определись с терминологией.
    А то как в детском саду - "а я имел в виду другое!"
  • Пробегал2... (27.04.08 13:00) [83]
    Игорь Шевченко ©   (27.04.08 12:35) [82]
    Что есть НЕПРАВИЛЬНО ?


    ну в принципе конечно можно понять неправильно с той точки зрения, что работать не будет правильно.

    Я имел в виду неправильно с той точки зрения, что так делать не принято, то есть плохой подход, хоть и может работать.
  • Игорь Шевченко © (27.04.08 13:29) [84]
    Пробегал2...   (27.04.08 13:00) [83]

    Насчет "работать не будет правильно" - если система позволяет, почему не будет работать ?


    > Я имел в виду неправильно с той точки зрения, что так делать
    > не принято, то есть плохой подход


    А если не секрет, где есть свод правил, где однозначно написано, как принято делать, как не принято, что есть плохой подход, что есть хороший подход ?
    То есть, я понимаю, что Дейкстра сказал "goto considered harmful" и это занесено на скрижали. Так вот, есть такие же скрижали относительно потоков в DLL ?

    Я до сих пор считал, если система позволяет нечто сделать, то вне зависимости от личных приверженностей, это нечто имеет право на существование.
  • Сергей М, (27.04.08 13:39) [85]

    > Экс-Оригинал   (26.04.08 22:53) [80]


    > т.е.?


    Что не понятно ?
  • Экс-Оригинал (27.04.08 13:45) [86]

    > Что не понятно ?


    Непонятно, что имеется ввиду. Что за другой  модуль?
  • Сергей М, (27.04.08 13:53) [87]

    > Что за другой  модуль?


    Любой PE-модуль в ВАП текущего процесса, содержащий исполняемый код поточной функции, адрес которой был указан при вызове CreateThread.
 
Конференция "Прочее" » Многопоточность и Delphi
Есть новые Нет новых   [134435   +34][b:0.001][p:0.004]