Форум: "Прочее";
Текущий архив: 2008.06.08;
Скачать: [xml.tar.bz2];
ВнизМногопоточность и 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;
Скачать: [xml.tar.bz2];
Память: 0.56 MB
Время: 0.043 c