Главная страница
Top.Mail.Ru    Яндекс.Метрика
Текущий архив: 2008.06.08;
Скачать: CL | DM;

Вниз

Многопоточность и Delphi   Найти похожие ветки 

 
Loginov Dmitry ©   (2008-04-23 23:32) [0]

Не так давно я достаточно негативно отзывался о реализации класса TThread в Delphi с завлением, что потоки при завершении иногда виснут. Все-таки тогда правда была не на моей стороне. Просто потоки крутятся в различных DLL-ках, и вполне закономерно, что при выгрузке с помощью FreeLibrary библиотеки, в которой еще крутятся потоки, происходят различные неприятные вещи.

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

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

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

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

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


 
ага   (2008-04-24 01:00) [1]


>
> Loginov Dmitry ©   (23.04.08 23:32)  

Какой ужжасс...


 
Tirael   (2008-04-24 02:15) [2]

а зачем отдельная ф-ция? перед выгрузкой же автоматически вызывается дллмайн с параметром длл_процесс_детач...


 
KSergey ©   (2008-04-24 07:35) [3]

Предлагаю в след. раз почитать Рихтера про потоки, процессы и т.д.
Т.к., с одной стороны, описаны стандартные грабли новичков, с другой - и, думаю, автор уже это понимает - все они исходят из вообще непонимания принципов работы потоков в виндовс, к которым TThread является лишь оберткой. Причем обертка эта такова, что она фактически очень слабо управляет собтвенно виндовыми объектами потоков, которыми, впрочем, "мощнее" управлять и нельзя по определению.
Однако легкость и податливость в управлении "с наружи" другими, "более простыми" компонентами, действительно сильно сбивает с толку при попытках так же "по простому" манипулировать экземпляром объекта TThread.


 
Loginov Dmitry ©   (2008-04-24 07:56) [4]

> описаны стандартные грабли новичков


Думаю, что и не только новичков. В той же степени это касается и профессионалов, еще не успевших по каким-либо причинам прочитать MSDN на все 100%


 
KSergey ©   (2008-04-24 08:54) [5]

> Loginov Dmitry ©   (24.04.08 07:56) [4]
> не успевших по каким-либо причинам
> прочитать MSDN на все 100%

В данном вопросе и объеме достаточно почитать Рихтера, эта тема у него очень подробно раскрыта. Могу ошибаться, проверять не буду, но пожалуй у него легко найти ответы на все вопросы из серии "че за нафик?" по всем упомянутым граблям.


 
Котик Б   (2008-04-24 09:26) [6]

Пользуйтесь напрямую CreateThread - в чём проблема то :)
Будете потом всё валить на WinAPI вместо TThread...


 
Сергей М. ©   (2008-04-24 10:01) [7]


> Loginov Dmitry ©   (23.04.08 23:32)


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


Возможно.
Невозможно другое - дождаться сигнала завершения потока в функции ожидания
Объясняется это довольно просто - в момент финализации dll-модуля PEB текущего процесса заблокирован и система не может внести туда изменения, касаемые TIB завершившегося потока, а флаг завершения потока система может поднять только после внесения этих изменений.
Т.е. в этой ситуации получается нечто вроде дедлока.


 
DiamondShark ©   (2008-04-24 15:14) [8]


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

Попользуйся -- узнаешь.


 
Пробегал2...   (2008-04-24 15:28) [9]

ну собственно что и требовалось доказать. Помню ту тему, где с апломбом утверждалось, что потоки в Delphi реализованы криво ;) Смешно. Просто кто-то не умеет их готовить.

Все остальное было сказано верно - читайте Рихтера, особенно про константы, передаваемые в главную функцию DLL. Никакой такой специальной функции экспортируемой библиотекой не надо, MS уже давно все продумала ;)


 
KSergey ©   (2008-04-24 15:44) [10]

> DiamondShark ©   (24.04.08 15:14) [8]
> Попользуйся -- узнаешь.

Суть не в том, что "узнаешь", а в том, кто виноват. :)


 
Loginov Dmitry ©   (2008-04-25 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 уже давно продумала"?


 
Экс-Оригинал   (2008-04-25 01:55) [12]

Что-то  непонятно, вообще о чем разговор.
Потоки вообще, не привязаны к какой-то конкретной билиотеке. К процессу приявязаны, если так можно выразиться.


 
Пробегал2...   (2008-04-25 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...   (2008-04-25 02:36) [14]

возможно ты не понимаешь как завершать потоки в DLL?

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

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

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

Это - правильная идеология работы.


 
Loginov Dmitry ©   (2008-04-25 07:58) [15]

> я не понимаю, откуда ты черпаешь информацию.


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

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


Но эта идеология не работает! Об этом в [0] как раз и идет разговор ;)


 
Игорь Шевченко ©   (2008-04-25 09:42) [16]


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


Какая разница, где завершать потоки (потоки Windows, а не Delphiйские TThread) ?


 
Loginov Dmitry ©   (2008-04-25 10:42) [17]

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


 
Сергей М. ©   (2008-04-25 10:48) [18]


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


Проиллюстрируй в коде ..


 
Loginov Dmitry ©   (2008-04-25 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


 
Сергей М. ©   (2008-04-25 11:25) [20]


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


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


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


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


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


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


 
guav ©   (2008-04-25 11:28) [21]

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

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


 
Loginov Dmitry ©   (2008-04-25 11:29) [22]


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


Я ведь когда-то тоже так думал! А оказывается, что есть откуда.
И именно на Sleep(0)...


 
Loginov Dmitry ©   (2008-04-25 11:29) [23]


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


Где именно?


 
Сергей М. ©   (2008-04-25 11:30) [24]


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


При объявлении MyThreadFunc ты указал соглашение stdcall  - вот граблями и получил)


 
guav ©   (2008-04-25 11:31) [25]

Смотри что передаёшь в BeginThread и что она требует.


 
guav ©   (2008-04-25 11:31) [26]

Кстати, если писать такое без @, то компилятор будет проверять прототип.


 
Сергей М. ©   (2008-04-25 11:32) [27]


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


То, что остановившись на этой строчке по брейкпойнту ты жмакнул F7 и получил AV, вовсе не означает, что именно эта строчка является причиной исключения.


 
Loginov Dmitry ©   (2008-04-25 11:42) [28]

Мда... Неудачный получился пример. Сорри за невнимательность :(
Но один фик с TThread AV гарантированно лезут...


 
Сергей М. ©   (2008-04-25 11:44) [29]


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


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


 
Loginov Dmitry ©   (2008-04-25 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.


Опять ведь найдете че-нибудь.... :)


 
Сергей М. ©   (2008-04-25 12:00) [31]


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


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


 
Loginov Dmitry ©   (2008-04-25 12:13) [32]


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


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

Таким образом получается, что в любом случае мы ловим или AV, или зависание.
А можно ли это как-то обойти без дополнительной экспортируемой функции? И как?


 
Сергей М. ©   (2008-04-25 12:39) [33]


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


Обойти в принципе можно, но реализация обхода будет уже из разряда трюков.
А трюки, как известно, штука рискованая и ненадежная)


 
Loginov Dmitry ©   (2008-04-25 12:52) [34]

кстати, в finalization такой трюк обычно спасает:


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


Но не всегда. В случае, когда FreeLibrary библиотеке не делают, при завершении работы процесса все-равно вызывается для всех DLL finalization, но к этому моменту винда уже успевает срубить все потоки, поэтому данный код приводит лишь к зависанию.


 
Сергей М. ©   (2008-04-25 12:58) [35]


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


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


 
Пробегал2...   (2008-04-25 13:40) [36]

Игорь Шевченко ©   (25.04.08 9:42) [16]
Какая разница, где завершать потоки (потоки Windows, а не Delphiйские TThread) ?


а зачем в дельфи лишать себя такого удобного класса, как TThread?


 
Loginov Dmitry ©   (2008-04-25 13:40) [37]


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


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


 
Игорь Шевченко ©   (2008-04-25 14:01) [38]

Пробегал2...   (25.04.08 13:40) [36]

Тут в ветке где-то про чтение Рихтера говорили. Нафига читать Рихтера, если он в Delphi ни ухом ни рылом ?


 
Сергей М. ©   (2008-04-25 15:16) [39]


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


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


И что ?

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

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

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


 
Пробегал2...   (2008-04-25 15:26) [40]

Loginov Dmitry ©   (25.04.08 13:40) [37]
В конце TMyThread.Execute (или в деструкторе) выполняется присвоение MyThread := nil


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

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


я пожалуй даже отвечать не буду.



Страницы: 1 2 3 вся ветка

Текущий архив: 2008.06.08;
Скачать: CL | DM;

Наверх




Память: 0.58 MB
Время: 0.016 c
2-1210753314
031178
2008-05-14 12:21
2008.06.08
Запрос в MS SQL


2-1210741269
noviceman
2008-05-14 09:01
2008.06.08
Уничтожение объекта из "своего" события.


11-1190048966
Seaniapeape
2007-09-17 21:09
2008.06.08
Нужен совет


2-1211177784
WebSQLNeederr
2008-05-19 10:16
2008.06.08
AnsiReplaceText несколько раз и разные замены - как?


2-1210598038
MVN
2008-05-12 17:13
2008.06.08
Firebird