Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Прочее";
Текущий архив: 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
2-1210639780
SadDragon
2008-05-13 04:49
2008.06.08
Движение точки по окружности


15-1208787695
Palladin
2008-04-21 18:21
2008.06.08
HDD


15-1208856014
samalex
2008-04-22 13:20
2008.06.08
Установка символа разделения целой и дробной части числа


15-1208952397
Ega23
2008-04-23 16:06
2008.06.08
Можно ли приблизительно оценить длину записи


15-1208936973
Kolan
2008-04-23 11:49
2008.06.08
Новости проекта DMClient.





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский