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

Вниз

Отловить завершение чужих потоков   Найти похожие ветки 

 
Crysis ©   (2016-05-17 16:43) [0]

Вопрос знатокам WinAPI (и желательно POSIX)

В произвольное время разные потоки дёргают мою функцию. Причём потоки могут быть созданы в отдельном Dll кем угодно.
В момент вызова функции я могу получить CurretThreadId.
Необходимо, зная эти ThreadId, навесить на потоки какой-нибудь калбек, который будет вызываться после завершения потока. С эксепшном или в штатном режиме.
Каким образом?

Опережая вопрос "зачем тебе это".
Есть несколько мультипоточных высоконагруженных проектов. Причём бывает потоки создаются в сторонней Dll.
Было решено поменять принципы обработки данных, задействуя threadvar и кеширование данных/буферов.
Но беда в том, что threadvar-области не финализируются при завершении потока.
Тот же TParallel постоянно создаёт и удаляет потоки, а у нас течёт память, причём серьёзно.


 
Pavia ©   (2016-05-17 19:18) [1]

Вешаешь Хук на EndThread


 
Crysis ©   (2016-05-17 20:21) [2]

Как?
Сработает ли это на потоки, которые созданы вне Delphi?


 
DayGaykin ©   (2016-05-17 21:43) [3]

Создаешь свой поток-следилку и там делаешь:
OpenThread+WaitForSingleObject.

Чтобы следить сразу за несколькими потоками: WaitForMultipleObjects и алгоритм посложнее. Тут можно добавить еще Event, чтобы можно было прерывать WaitForMultipleObjects и обновлять список слежения.

Чтобы следить за более чем MAXIMUM_WAIT_OBJECTS потоками, необходимо создать несколько потоков-следилок.


 
Crysis ©   (2016-05-17 22:39) [4]

> DayGaykin ©   (17.05.16 21:43) [3]

Такой подход наверное тоже некорректен.
Калбек по идее должен вызываться в завершающемся потоке. Потому что нужен доступ к TLS.
В предложенном тобой варианте - я его не увижу.


 
NoUser ©   (2016-05-18 00:03) [5]

DLL_THREAD_DETACH
>  который будет вызываться после завершения потока
в том же потоке.


 
Crysis ©   (2016-05-18 00:06) [6]

> NoUser ©   (18.05.16 00:03) [5]

Но у меня же не Dll
Точнее не обязательно Dll


 
NoUser ©   (2016-05-18 00:19) [7]

PS.
> Было решено поменять принципы обработки

А если у потока после работы с функцией еще много планов на дальнейшую жизнь, то "ваша" память будет ему в подарок?


 
NoUser ©   (2016-05-18 00:23) [8]

> Но у меня же не Dll
ну, добавьте ещё и dll


 
DayGaykin ©   (2016-05-18 00:27) [9]


> Crysis ©   (17.05.16 22:39) [4]
> > DayGaykin ©   (17.05.16 21:43) [3]
>
> Такой подход наверное тоже некорректен.
> Калбек по идее должен вызываться в завершающемся потоке.
>  Потому что нужен доступ к TLS.
> В предложенном тобой варианте - я его не увижу.

Тогда нормально никак.

Можно, конечно извратиться так:
Перехватываешь createthread своего процесса. Вместо точки входа указываешь свою функцию, которая: навесит SEH фрейм и перейдет по исходной точке входа. А дальше должно быть понятно, если знать что такое SEH фрейм.


 
Crysis ©   (2016-05-18 00:28) [10]

> А если у потока после работы с функцией еще много планов
> на дальнейшую жизнь, то "ваша" память будет ему в подарок?


В том то и дело
"В подарок" только на время работы. А по завершению потока - память надо грамотно забрать.


 
Crysis ©   (2016-05-18 00:30) [11]

> Перехватываешь createthread своего процесса. Вместо точки
> входа указываешь свою функцию, которая: навесит SEH фрейм
> и перейдет по исходной точке входа. А дальше должно быть
> понятно, если знать что такое SEH фрейм.


Тогда уж не CreateThread, а ExitThread
Но я боюсь, Касперский заблокирует наш софт у клиентов. Или иные антивирусы будут ругаться.


 
DayGaykin ©   (2016-05-18 00:32) [12]


> Crysis ©   (18.05.16 00:30) [11]

В ExitThread может и не настать.


> Но я боюсь, Касперский заблокирует наш софт у клиентов.
> Или иные антивирусы будут ругаться.

В своем процессе ты можешь делать что хочешь.


 
Rouse_ ©   (2016-05-18 12:00) [13]

Тогда лучше ZwCreateThread перехватить и там свою потоковую процедуру с SEH.


 
Leonid Troyanovsky ©   (2016-05-18 13:27) [14]


> Crysis ©   (17.05.16 16:43)

>  Причём бывает потоки создаются в сторонней Dll.Было решено
> поменять принципы обработки данных, задействуя threadvar
> и кеширование данных/буферов.Но беда в том, что threadvar-
> области не финализируются при завершении потока.

threadvar - здесь костыль.

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

В случае если код потоков менять нельзя, то память для
потоков может выделять твоя функция, при условии, что
один (или более поток) приложения будет следить путем
WaitForMultipleObjects за рабочими и по их завершении
освобождать связанную с ними память.

Про синхронизацию доступа к общим ресурсам промолчу,
бо, с этим, наверное, все в порядке.

--
Regards, LVT.


 
Leonid Troyanovsky ©   (2016-05-18 14:02) [15]


> Leonid Troyanovsky ©   (18.05.16 13:27) [14]

> будет следить путем WaitForMultipleObjects за рабочими и
> по их завершении освобождать связанную с ними память.

Схему можно упростить до следующей:

Функция ведет список (ThreadList) троек: ThreadId,
OTHandle(хендл OpenThread),
Data(указ. на распред. память).

Когда список (или память) переполнится, проходим по списку,
проверяем состояние OTHandle (f.e., GetThreadExitCode),
утилизируем память, CloseHandle, удаляем лишнее.

--
Regards, LVT.


 
Crysis ©   (2016-05-18 14:56) [16]

> Rouse_ ©   (18.05.16 12:00) [13]
> Тогда лучше ZwCreateThread перехватить и там свою потоковую
> процедуру с SEH.


А можно поподробнее?
Подойдёт ли этот способ для потоков, созданных вне Delphi?


 
NoUser ©   (2016-05-18 17:14) [17]

> А можно поподробнее?
Лучше расскажи/покажи как это "созданные кем угодно в отдельном Dll" потоки дёргают функцию в твоём exe ?


 
Crysis ©   (2016-05-18 17:33) [18]

> NoUser ©   (18.05.16 17:14) [17]

А что удивительного? Передаётся калбек, он вызывает в отдельных потоках.


 
NoUser ©   (2016-05-18 17:50) [19]

> Передаётся калбек
И что, внутри калбека каждому "новенькому" потоку даруется безвозмездно кусок памяти? - хорошенький "принцип обработки данных" !


 
Crysis ©   (2016-05-18 18:42) [20]

> NoUser ©   (18.05.16 17:50) [19]

Ну какое значение имеет, даём мы кому-то кусок памяти или нет...
Есть особенность использования. Если завершения всех потоков не получится отследить - я сделаю через SystemThreadEndProc для своих потоков и более сложный и медленный вариант для чужих. Но хочется универсально.


 
DayGaykin ©   (2016-05-18 19:59) [21]

Facepalm


 
NoUser ©   (2016-05-18 20:05) [22]

> Но хочется универсально.
Сделай второй калбек (для зачистки), сделай свою длл-ку в которую "передай" его и там вызывай в момент DLL_THREAD_DETACH.


 
Rouse_ ©   (2016-05-18 22:54) [23]


> Crysis ©   (18.05.16 14:56) [16]
> А можно поподробнее?
> Подойдёт ли этот способ для потоков, созданных вне Delphi?

Что значит "вне Delphi"?
Так ты перехватишь создание нити в твоем приложении (за исключением случая CreateRemoteThread из другого процесса)


 
Crysis ©   (2016-05-19 12:01) [24]

> Rouse_ ©   (18.05.16 22:54) [23]

Допустим я использую сторонний движок, будь то звуковая библиотека, физика, базы данных. Они написаны на другом языке программирования, исходников нет; зато известно, что они вызывают CreateThread и внутри потока дёргают мой калбек. "Перехват ZwCreateThread" поможет в данной ситуации или нет? Можно поподробнее, как перехватить?


 
Rouse_ ©   (2016-05-19 16:04) [25]

Поможет если будешь делать перехват сплайсингом делать.
http://alexander-bagel.blogspot.ru/2013/01/intercept.html
http://alexander-bagel.blogspot.ru/2013/05/intercept2.html


 
Crysis ©   (2016-05-19 17:41) [26]

Спасибо, буду пробовать!


 
han_malign ©   (2016-05-19 17:48) [27]


> решено поменять принципы обработки данных, задействуя threadvar
> и кеширование данных/буферов

- кеширование внешнего контекста в реентерабельной функции - нонсенс...
А промежутоные буфера надо выделять на стеке(в пределах отведенных(по умолчанию) ~ 1Мб естественно)...


 
DayGaykin ©   (2016-05-19 20:32) [28]


> Rouse_ ©   (19.05.16 16:04) [25]

А не проще перехватить импорт DLL?


 
Rouse_ ©   (2016-05-19 20:37) [29]


> DayGaykin ©   (19.05.16 20:32) [28]
> А не проще перехватить импорт DLL?

Он только для статической линковки, используется.


 
DayGaykin ©   (2016-05-19 20:39) [30]


> Rouse_ ©   (19.05.16 20:37) [29]

Со сплайсингом в многопоточности точно не без проблем обойдется, да еще если на 64 битах запускать.


 
Rouse_ ©   (2016-05-19 21:10) [31]

Если правильно делать с использование хотпатча (атомарной замены 2 байт в области нопов  в прологе функции) - никаких проблем не будет. Этож штатный механизм.


 
Rouse_ ©   (2016-05-19 21:17) [32]

В частности в 64 битах делается воот так:

Вот тело ZwCreateThread (адрес точки входа 77AFD890), перед ней идет пятибайтный длинный NOP

77AFD88B: 0F 1F 44 00 00                            nop dword ptr [rax+rax+00h]
77AFD890: B8 DB 3C 03 00                            mov eax, 00033CDBh
77AFD895: 48 63 C0                                  movsxd rax, rax
77AFD898: FF E0                                     jmp rax
77AFD89A: C3                                        ret


первым шагом делаем установку джампа в область длинного нопа,
вторым шагом - атомарно (через LOCK XCHG) джамп на эту область вместо инструкции JMP RAX (для других функций вместо него будет SYSCALL)


 
DayGaykin ©   (2016-05-20 03:33) [33]


> Rouse_ ©   (19.05.16 21:10) [31]
> Если правильно делать с использование хотпатча (атомарной
> замены 2 байт в области нопов  в прологе функции) - никаких
> проблем не будет. Этож штатный механизм.

Если я правильно помню, условия хот-патча не выполняются если программа запущена в 64хбитной ос.


 
Leonid Troyanovsky ©   (2016-05-20 08:40) [34]


> Leonid Troyanovsky ©   (18.05.16 14:02) [15]

Вот набросал пример, без всякой оптимизации и контроля ошибок.

type
  TMItem = packed record
    ThreadId: Cardinal;
    OTHandle: THandle;
    Data: DWord;
  end;
  PMItem = ^TMItem;

var
 ftl: TThreadList;

function GetDataPtr: PDWord;
var
 i: Longint;
 pmi: PMItem;
 exCode: Cardinal;
begin
 Result := nil;
 with ftl.LockList do
   try
     for i:= Count-1 downto 0 do
       begin
         pmi := PMItem(List[i]);
         if pmi.ThreadId = GetCurrentThreadId then
           begin
             Result := @pmi.Data;
             Continue;
           end;

         GetExitCodeThread(pmi.OTHandle, exCode);
         if ExCode <> STILL_ACTIVE then
           begin
             OutputDebugString(PChar(IntToStr(ExCode)));
             CloseHandle(pmi.OTHandle);
             Dispose(pmi);
             Delete(i);
           end;
       end;

     if Result = nil then
       begin
         New(pmi);
         pmi.ThreadId := GetCurrentThreadId;
         DuplicateHandle( GetCurrentProcess,
                          GetCurrentThread,
                          GetCurrentProcess,
                          @pmi.OTHandle,
                          0,
                          False,
                          DUPLICATE_SAME_ACCESS);
         pmi.Data := 0;
         Add(pmi);
         Result := @pmi.Data;
       end;
   finally
     ftl.UnlockList;
   end;
end;

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

procedure TMyThread.Execute;
var
 pData: PDWord;
begin
 pData := GetDataPtr;
 pData^ := Random(10000);
 Sleep (pData^);
 ReturnValue := pData^;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
 ftl := TThreadList.Create;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
 TMyThread.Create(False).FreeOnTerminate := True;
end;

Смотрим в EventLog.

--
Regards, LVT.


 
Rouse_ ©   (2016-05-20 09:13) [35]


> DayGaykin ©   (20.05.16 03:33) [33]
> Если я правильно помню, условия хот-патча не выполняются
> если программа запущена в 64хбитной ос.

Все выполняется иначе как бы перехваты в моем софте работали?


 
Rouse_ ©   (2016-05-20 09:37) [36]

А, или ты про 32 битный софт в 64 битной ОС? Да, там некоторые функции идут без пролога, но там тоже можно пошаманить


 
DayGaykin ©   (2016-05-20 13:32) [37]


> Rouse_ ©   (20.05.16 09:37) [36]
> А, или ты про 32 битный софт в 64 битной ОС? Да, там некоторые
> функции идут без пролога, но там тоже можно пошаманить

Да-да. Ты мне про это, кстати, и говорил.


 
Cobalt ©   (2016-05-25 11:53) [38]

Почему бы не поменять способ работы на что-то типа
BeginUpdate;
EndUpdate;

?



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

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

Наверх




Память: 0.57 MB
Время: 0.007 c
15-1463492615
Crysis
2016-05-17 16:43
2017.07.16
Отловить завершение чужих потоков


11-1265717864
tippa
2010-02-09 15:17
2017.07.16
runtime error 216


15-1463888094
Кто б сомневался
2016-05-22 06:34
2017.07.16
Zlib - TZDecompressionStream.Seek написан криво


15-1463911682
MsGuns
2016-05-22 13:08
2017.07.16
VS-2014/MS SQL Server


2-1442377244
vegarulez
2015-09-16 07:20
2017.07.16
Twebbrowser+IIHTMLDocument2 парсинг + innerhtml ?