Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Прочее";
Текущий архив: 2008.04.20;
Скачать: [xml.tar.bz2];

Вниз

Подсчет количества вхождений потоков   Найти похожие ветки 

 
Пробегал...   (2008-03-03 01:07) [0]

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

Но ведь возможна такая ситуация, что будет исполняться метод ядра какой-то (проверка уже прошла), и в это время начнется уничтожения ядра. Возможны всякие непредсказуемые коллизии.

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

Теперь вопрос как это реализовать. Самое простое - это обычный флаг типа boolean и счетчик типа integer. Вроде бы операции: inc / dec выполняются в один такт и тут коллизий не будет, но плохо что операции проверки флага и счетчика разделены во времени. Пример:

function Core.SuperFunc: boolean;
begin
 if FlagRun then
 begin
   inc(counter);
   try
      ....
      .....
   finally
     dec(counter);
   end;
 end
 else
   Result := false ;
end;


Хоть и маловероятна, но возможна какая ситуация. Один поток пройдет проверку FlagRun, но не успеет до inc(counter). А в это время произойдет уничтожение ядра, которое выставит сначала FlagRun а false, а потом проверит значение счетчика:

FlagRun := false;
while counter > 0 then
 Sleep(200);


Но понятно, что тот удачливый поток уже прошел проверку FlagRun, а значение счетчика еще не увеличил. Понятно, он может стать головной болью, хоть и очень маловероятной.

И как бы это все сделать в одно движение, по грамотному?

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


 
Игорь Шевченко ©   (2008-03-03 01:10) [1]

RTFM Interlocked functions


 
Пробегал...   (2008-03-03 01:19) [2]

Игорь Шевченко ©   (03.03.08 1:10) [1]

хм... А разве inc / dec могут привести к конфликтам в многопоточном приложении? Я думал они в один такт выполняются и даже если несколько процессоров - конфликта не будет. Ну да ладно, как я понял это обыкновенные inc / dec, только обрамленные входом / выходом в критическую секцию, верно? Но проблему это не решает. ну будет так:

function Core.SuperFunc: boolean;
begin
if FlagRun then
begin
  InterlockedIncrement(counter);
  try
     ....
     .....
  finally
    InterlockedDecrement(counter);
  end;
end
else
  Result := false ;
end;


все равно проблема описанная остается. Ведь между проверкой флага и увеличением счетчика поток может потерять управление... хотя я туплю, придумал


 
Zeqfreed ©   (2008-03-03 01:29) [3]

Если даже особо мозги не напрягать, то можно делать безусловный Inc(Counter, Flag). Хотя, наверное, можно что-нибудь более красивое придумать :)


 
Игорь Шевченко ©   (2008-03-03 01:32) [4]


> Ведь между проверкой флага и увеличением счетчика поток
> может потерять управление


RTFM Interlocked functions


 
korneley ©   (2008-03-03 01:36) [5]

Не нравятся семафоры - см. критические секции. (хотя по сути есть одно и тоже, имхо)
cs.enter;
if FlagRun then inc(counter);
cs.leave;
if counter > 0 then
try
...
finally
 dec(counter);
end;

cs.enter;
FlagsRun := false;
cs.leave;
while counter > 0 do ...

Не проверялось, но суть в том, что изменение флага в одном месте и (проверка флага + действия по результатам проверки, в другом) в одной критической секции.


 
Пробегал...   (2008-03-03 01:36) [6]

Видимо, так надо написать:

cs: TCriticalSection ;

...

function Core.SuperFunc: boolean;
begin
 cs.Enter ;
 try
   if FlagRun then
     inc(counter)
   else
   begin
     Result := false ;
     exit ;
   end;
 finally
   cs.Leave ;
 end;
 try
   ....
   .....
 finally
   cs.Enter ;
   dec(counter);
   cs.Leave ;
 end;
end;


а соответственно при уничтожении ядра делать так:

function Core.Destroy...
begin
 FlagRun := false;
 FlagClear := false ;
 repeat
   cs.Enter;
   try    
     if counter < 1 then FlagClear := true ;
   finally
     cs.Leave ;
   end;
 if not FlagClear then Sleep(300) ;
 until FlagClear ;
end;


покритикуйте? Осталось избавиться от sleep...


 
Пробегал...   (2008-03-03 01:40) [7]

Игорь Шевченко ©   (03.03.08 1:32) [4]
RTFM Interlocked functions


может хватит уже? Я посмотрел, нету в дельфи такого раздела, в том числе и по Win32SDK. Нашел функции InterlockedIncrement / InterlockedDecrement. О чем и написал. Также написал почему мне это не совсем катит.

А если сложно написать что-то более вразумительное - то лучше вообще ничего не писать.


 
Пробегал...   (2008-03-03 01:43) [8]

На MSDN кстати по этому слову тоже ищутся только функции:

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

InterlockedIncrement
InterlockedDecrement
InterlockedExchange
InterlockedTestExchange
InterlockedCompareExchange
InterlockedCompareExchangePointer
InterlockedExchangePointer
InterlockedExchangeAdd


я не понимаю как они мне могут помочь. Как альтернатива inc / dec понимаю. Но в комплексе проблему не решают.

Поэтому или кому-то надо уточнять, или кто-то даже не читал чуть моей проблемы. Спасибо.


 
korneley ©   (2008-03-03 01:45) [9]

не... при уничтожении, не проверку counter надо в секцию пихать, а  изменение FlagRun, как минимум...


 
Пробегал...   (2008-03-03 01:52) [10]

korneley ©   (03.03.08 1:45) [9]
при уничтожении, не проверку counter надо в секцию пихать, а  изменение FlagRun, как минимум


а зачем?


 
korneley ©   (2008-03-03 01:58) [11]

Еще раз повторю: Изменение флага (FlagRun := ...) и проверку в другом потоке этого флага + инкремент счетчиков по результату проверки, это все, что надо засинхонизировать. Остальное приложится. Наверное :)))


 
korneley ©   (2008-03-03 02:04) [12]


> а зачем?

А затем, что флаг изменится, а поток согласно ситуации в [0] успеет раньше его прочитать, т.к. изменение флага не в секции. И по барабану, что проверка в ней.


 
Пробегал...   (2008-03-03 02:05) [13]

korneley ©   (03.03.08 1:58) [11]
Еще раз повторю: Изменение флага (FlagRun := ...) и проверку в другом потоке этого флага + инкремент счетчиков по результату проверки


я тоже могу повторить: зачем изменение флага в деструкторе вносить в критич. секцию? Приведи пример, когда код [6] сработает неправильно? Очень похоже на веру, типа вроде тебе так кажется подсознательно, поэтому так и надо ;)


 
Пробегал...   (2008-03-03 02:09) [14]

korneley ©   (03.03.08 2:04) [12]
А затем, что флаг изменится, а поток согласно ситуации в [0] успеет раньше его прочитать, т.к. изменение флага не в секции


ну успеет, и что дальше? Он прочтет True и пойдет дальше, но к тому времени как можно заметить он уже будет в критической секции, а посему точно увеличит счетчик на единицу. А значит в деструкторе проверка в критической секции на counter не сработает и деструктор будет ждать пока поток не отработает.


 
ЦУП ©   (2008-03-03 02:09) [15]

Схематично:
function Core.SuperFunc: boolean;
begin
 EnterCriticalSection(...);
   if FlagRun then Inc(Counter);
 LeaveCriticalSection(...);

 Result := FlagRun;

 if Result then
 begin
   try
     ....
     .....
   finally
     InterLockedDecrement(Counter);
   end;
 end;
end;


 
korneley ©   (2008-03-03 02:22) [16]


> Приведи пример, когда код [6] сработает неправильно?

Да не, скорее всего сработает:
Поток: if FlagRun, получает true, деструктор FlagRun :=  false, дальше... конечно в секцию не войдет, а когда войдет, counter уже приплюсован... Ну ладно, будем считать это моими пристрастиями :)


 
Пробегал...   (2008-03-03 02:34) [17]

ЦУП ©   (03.03.08 2:09) [15]
Схематично:
function Core.SuperFunc: boolean;


ну я вообще-то написал это уже в посте [6] ;) Разница только в том, что мое:

cs.Enter ;
dec(counter);
cs.Leave ;


ты заменил на:

InterLockedDecrement(Counter);

Самое главное я ступил и не догадался, что проверку на флаг и увеличение счетчика надо просто делать в критической секции ;)


 
korneley ©   (2008-03-03 02:35) [18]

Главное - обозначились пути решения [0] ;)


 
ЦУП ©   (2008-03-03 02:39) [19]


> Пробегал...   (03.03.08 02:34) [17]


С семафором будет значительно проще.


 
Пробегал...   (2008-03-03 02:43) [20]

вопрос один - как в моем коде [6] избавиться от sleep?

По идее нужно использовать какую-нибудь WaitForXXX, но с каким-объектом? Мне нужна противоположность семафору. Семафор занят, когда набилось n-ое количество потоков/вхождений и свободен когда есть хотя бы еще одно "место". А мне нужно чтобы объект был занят, пока занято хотя бы одно место и освободился только когда счетчик вернулся в исходное положение...


 
Пробегал...   (2008-03-03 02:59) [21]

ЦУП ©   (03.03.08 2:39) [19]
С семафором будет значительно проще.


я не представляю как семафор может мне помочь?

Допустим, при каждом вхождении в метод будет вызываться OpenSemaphore, а при выходе из метода CloseHandle, отлично.

Но как в деструкторе сделать остановку, пока счетчик не будет приведен в первоначальное состояние? Именно это нужно, именно когда счетчик вернется в первоначальное состояние - это значит все потоки "ушли". Но идеология семафора другая - он занят только когда счетчик в ноль... Я, наверное, могу сделать что-то типа:

function Core.Destroy...
var
 Count: integer ;
begin
 FlagRun := false ;
 repeat
   ReleaseSemaphore(h, 0, @count) ;
 if Count <> cStartCount then
   sleep(300) ;
 until Count = cStartCount ;
end;


но во-первых, я не уверен, что тако финт ушами задокументирован:

ReleaseSemaphore(h, 0, @count) ;

а по-другому я даже не знаю как узнать значение счетчика семафора без изменения его состояния.

И во-вторых, между:

FlagRun := false ;
и
ReleaseSemaphore(h, 0, @count) ;

пройдет время, за которое другой поток может уже обойти проверку флага, но семафор еще не затронуть. То есть, тоже надо все обрамлять в критич. секции. А это уже получается ничуть не проще варианта в [6].

И самое главное это не избавляет от sleep ;(


 
Пробегал...   (2008-03-03 03:03) [22]

Пробегал...   (03.03.08 2:59) [21]
Допустим, при каждом вхождении в метод будет вызываться OpenSemaphore, а при выходе из метода CloseHandle, отлично


точнее при каждом вхождении в метод будет вызываться WaitForSingleObject для уменьшения счетчика на 1, а при завершении метода вызываться ReleaseSemaphore для увеличения счетчика на 1.

При том, что при создании семафора заведомо будет указано большое lInitialCount, чтобы счетчик не ушел в ноль, тогда методы ядра заблокируются...


 
Пробегал...   (2008-03-03 04:11) [23]

в общем, придумал только так:

cs := TCriticalSection.create ;
event := CreateEvent(nil, true, true, nil) ;
counter := 0 ;


Каждый метод оборачиваем:

function Core.SomeFunc: boolean;
begin
 cs.Enter ;
 try
   if FlagRun then
   begin
     inc(counter);
     if counter = 1 then
       ResetEvent(Event) ;
   end
   else
   begin
     Result := false ;
     exit ;
   end;
 finally
   cs.Leave ;
 end;
 try
   РАБОТА МЕТОДА !!!
 finally
   cs.Enter ;
   dec(counter);
   if counter = 0 then
     SetEvent(Event) ;
   cs.Leave ;
 end;
end;


а соответственно код деструктора такой:

function Core.Destroy...
begin
 cs.Enter;
 try
   FlagRun := false;
 finally
   cs.Leave ;
 end;
 WaitForSingleObject(Event,  INFINITE) ;
 CloseHandle(Event) ;
 ...
 ...
end;


Пожалуйста, критика? Скажите хоть что все верно и работать будет ;)

P.S. Какой же это геморой писать потокобезопасные классы... Прорва кода в каждом методе только лишь для безопасности, хотя сам метод может быть из одного вызова функции состоит ;)


 
Игорь Шевченко ©   (2008-03-03 09:56) [24]

Пробегал...   (03.03.08 01:40) [7]


> А если сложно написать что-то более вразумительное - то
> лучше вообще ничего не писать.


http://ln.com.ua/~openxs/articles/smart-questions-ru.html

Читать наизусть до полного и окончательного просветления. Если не поможет - буду делать прививку от хамства.


 
Пробегал...   (2008-03-03 16:13) [25]

Игорь Шевченко ©   (03.03.08 9:56) [24]

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

В результате расточительная трата времени двух человек. Зачем?

Я просто подожду людей не таких профи, ответы которых мне возможно будут понятны. Вот и все. Не вижу проблем и вещей на которые можно обижаться.

С уважением.


 
Пробегал...   (2008-03-03 16:14) [26]

Народ! Прокоменьте плиз мой пост [23] ? Вроде все верно, но как-то хочется взгляда со стороны ;)


 
jack128_   (2008-03-04 11:11) [27]


>  но как-то хочется взгляда со стороны ;)

со сторону - очень сложно и криво выглядит.  Вот как на interlocked-функциях. Не тестил, но вроде должно работать

procedure DoWork;
begin
 if Finish > 0 then
   Exit;
 InterlockedIncrement(ThreadCount);
 try
   // do work
 finally
   InterlockedDecrement(ThreadCount);
 end;
end;

procedure Destroy;
begin
 // выставляем флаг, чтобы не начинали работу новые потоки
 InterlockedExchange(Finish, 1);
 // Ждем, пока закончат работу уже запустившееся потоки
 while InterlockedCompareExchange(ThreadCount, 0, 0) > 0 do
   Sleep(0);
 // Do destroy
end;

PS А Игоря ты зря не слушаешь.


 
jack128_   (2008-03-04 11:15) [28]

собственно я тоже что то перемудрил. наверно мона так:

procedure Destroy;
begin
// выставляем флаг, чтобы не начинали работу новые потоки
Finish := 1;
// Ждем, пока закончат работу уже запустившееся потоки
while ThreadCount > 0 do
  Sleep(0);
// Do destroy
end;


 
jack128_   (2008-03-04 11:39) [29]

блин, нет, все не правельно.  Но как то мона реализовать чисто на интерлоках, нутром чую :-)


 
Пробегал...   (2008-03-04 15:58) [30]

jack128_   (04.03.08 11:39) [29]
блин, нет, все не правельно.  Но как то мона реализовать чисто на интерлоках, нутром чую :-)


Жень, ну вот видишь. Если уж мастер дельфи не смог составить пример, исходя из советов ИШ"а, то на что мне рассчитывать, глупому новичку? ;)

jack128_   (04.03.08 11:11) [27]
А Игоря ты зря не слушаешь


да я слушаю, просто пользы нет. До сих пор пользы не нашел, а уж если еще и ты не смог сделать ;) Насчет сложности - согласен, немного дико выглядит, но я оформил вот так:

class function TCore.ThreadSafeEnter: boolean;
begin
 coreCriticalSection.Enter ;
 try
   if CoreFlagRun then
   begin
     inc(CoreCounterThread);
     if CoreCounterThread = 1 then
       ResetEvent(coreEvent) ;
     Result := true ;
   end
   else
   begin
     Result := false ;
     exit ;
   end;
 finally
   coreCriticalSection.Leave ;
 end;
end;

class procedure TCore.ThreadSafeLeave;
begin
 coreCriticalSection.Enter ;
 try
   dec(CoreCounterThread);
   if CoreCounterThread = 0 then
     SetEvent(coreEvent) ;
 finally
   coreCriticalSection.Leave ;
 end;
end;


А каждый метод ядра делает что-то вроде:

Result := ThreadSafeEnter ;
if not Result then exit ;
try
 РАБОТАЕМ
finally
 ThreadSafeLeave
end;


Ну и в деструкторе просто выставляется флаг, после чего ожидается наступление события coreEvent, которое означает что последний поток "ушел" из методов ядра.


 
Игорь Шевченко ©   (2008-03-04 16:29) [31]

Пробегал...   (04.03.08 15:58) [30]


> да я слушаю, просто пользы нет


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


 
jack128_   (2008-03-04 17:02) [32]


>  Если уж мастер дельфи не смог составить пример,

потому что данный дельфи мастер сегодня туп как пробка ;-)  
Это чистая алгоритмика, тут _знания_ никак не помогут. Собственно их  и нет, я тока сегодня попробывал испольковать interlocked"ы.

Самый простой вариант - это пост [3], только проверку флага делать ВНУТРИ блока try - finally - end.
Правда в таком случае - я могу заставить ядро не уничтожаться никогда просто запуская новые потоки чаще, чем ядро проверяет значение счетчика потоков..


 
jack128_   (2008-03-04 17:04) [33]


> это пост [3],

[2] естественно


 
Пробегал...   (2008-03-04 18:36) [34]

jack128_   (04.03.08 17:02) [32]

ну в общем я все таки склоняюсь к своему конечному решению [30]. Не так просто, но зато вроде должно работать на ура. И при этом используются эвенты, что избавляет от циклов навроде:

while not (проверка) do
 sleep(XXX);


и не надо подбирать это XXX оптимальное и систему не загружает излишне ;)


 
ага   (2008-03-04 19:42) [35]

Фигней страдаешь. Сначала нать завершить потоки, потом из майн валить ядро. Ну а так

var
 CoreClients: integer;
 CoreTerminate: boolean = false;

function SomeCoreFunction...
begin
 Result:= false;
  if CoreTerminate the Exit;
  InterLockedIncrement(CoreClients);
 try
...
 finally
   InterLockeddecrement(CoreClients);
 end;
end;

procedure FinalizeCore
begin
 CoreTerminate:= true;
 while InterLockedCompareExcenge(CoreClients, 0, 0) <> 0 do Sleep(0);
 DestroyCore();
end;


 
ага   (2008-03-04 19:45) [36]

Не, так:

function SomeCoreFunction...
begin
Result:= false;
InterLockedIncrement(CoreClients);
try
  if CoreTerminate the Exit;
  ...
finally
  InterLockeddecrement(CoreClients);
end;
end


 
Пробегал...   (2008-03-04 20:15) [37]

ага   (04.03.08 19:42) [35]
while InterLockedCompareExcenge(CoreClients, 0, 0) <> 0 do Sleep(0);


на мой взгляд фиговый код.


 
ага   (2008-03-04 20:29) [38]


> Пробегал...   (04.03.08 20:15) [37]
> ага   (04.03.08 19:42) [35]
> while InterLockedCompareExcenge(CoreClients, 0, 0) <> 0
> do Sleep(0);
>
> на мой взгляд фиговый код.

Ню-ню. Сразу чуйствуется - специалист!


 
ага   (2008-03-04 20:44) [39]


> Сразу чуйствуется - специалист!

Ага. Особливо по [30]. Молодец, так и делай. Главное - ниче не меняй.


 
ага   (2008-03-04 20:51) [40]

ЗЫ

> if CoreCounterThread = 0 then

Эт так, мелочь. По сравнинию с остальным.


 
ага   (2008-03-04 20:52) [41]

Тьфу ты, эту имел в виду

> if CoreCounterThread = 1 then


 
ЦУП ©   (2008-03-05 01:06) [42]


> ага   (04.03.08 19:42) [35]
> Фигней страдаешь. Сначала нать завершить потоки, потом из
> майн валить ядро. Ну а так



> ага   (04.03.08 19:45) [36]
> Не, так:



> ага   (04.03.08 20:51) [40]
> ЗЫ> if CoreCounterThread = 0 thenЭт так, мелочь. По сравнинию
> с остальным.



> ага   (04.03.08 20:52) [41]
> Тьфу ты, эту имел в виду> if CoreCounterThread = 1 then


Ты сам определись сначала, а потом советуй что-то.


 
Пробегал...   (2008-03-05 01:13) [43]

ага   (04.03.08 20:29) [38]
Ню-ню. Сразу чуйствуется - специалист!


код:

while InterLockedCompareExcenge(CoreClients, 0, 0) <> 0 do Sleep(0);

плох тем, что пока будет ждать установления нуля - загрузит процессор (или по-крайней мере одно ядро процессора) на 100%. То есть, такой примитивный цикл будет кушать очень много вычислительных ресурсов и притормаживать другие потока такого же или не дай бог меньшего приоритета.

Также InterlockedCompareExchange существует только под Windows NT, а моя программа расчитана на работу во всей линейке win9x / winNT

А чем мой код плох?

ага   (04.03.08 20:52) [41]
> if CoreCounterThread = 1 then


и что тебе не нравится? Первый зашедший поток очищает событие и пока CoreCounterThread опять не станет нулем - событие не произойдет. Можно было написать:

if CoreCounterThread > 0 then

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

Не понял, что не так, все будет работать на ура. Более того, уже работает.


 
ага   (2008-03-05 04:00) [44]

2 ЦУП

Вот как раз тя забыл спросить, че мне делать сначала, а че потом. Всех спросил, а тя забыл.

2 Проходил

Да, загрузит. А чем загрузит-то? Работой потоков в ядре. Это будет самая быстрая реакция на выход всех потков из твово ядра. И он применим там, где применим. Приоритеты?  А ты че, сам не знаешь какие у тя приоритеты потоков? Разные - не используй Sleep(0). Другое ограничение - ежель потоки в ядре будут че-нитьб ждать. Эт че, все нать разжевывать? Самому не дотумкаться, что показан принцип, а не готовый код.

А правильное решение я те уж сказал - первой же строкой.

> Не понял, что не так, все будет работать на ура. Более того,
>  уже работает.

Ага, будет, как же. Расскажи эт своей бабушке. Ладно, я с утра добрый.

Вот у тя поток T1 вызвал ThreadSafeEnter. Выполняется:

coreCriticalSection.Enter ;
try
  if CoreFlagRun then
  begin

Тут его прерывает система и начинает работать поток T2, котрый вызыват деструктор ядра:

> Ну и в деструкторе просто выставляется флаг, после чего
> ожидается наступление события coreEvent


CoreFlagRun:= false;
WaitForSingleObject(coreEvent...

В каком состоянии будет coreEvent? Догадайся с трех раз. Эт если потоки переключаются. А ежель на многопроцессорной тачке, то те же гонки, вид сбоку, тока вероятность еще выше.

Дыра, и когда ты в нее попадешь - вопрос времени. А то что попадешь - к бабкам не ходи.


 
Семеныч   (2008-03-05 06:30) [45]

1. В ядре создаем ThreadList и делаем его доступным всем потокам.

2. Каждый поток имеет FreeOnTerminate = True.

3. Каждый поток имеет такой Execute:

procedure TMyThread.Execute;
begin
 ThreadList.Add(Self);
 try
   DoWork;
 finally
   ThreadList.Remove(Self);
 end;
end;


4. Завершение работы:

а). если метод DoWork потоков проверяет свойство Terminated, то сначала ядро приказывает всем потокам завершиться:

with ThreadList, LockList do
try
 for i := 0 to Count - 1 do
   TMyThread(Items[i]).Terminate;
finally
 UnlockList;
end;

Этот код безопасен, потому что перед его выполнением ядро залочило список. Поэтому при выполнении этого кода ни один поток завершиться не сможет (так как будет ждать, пока ядро не освободит список) - значит, Count не изменится.

Если метод DoWork потоков свойство Terminated не проверяет, то этот код не нужен (хотя и не помешает).

б). ядро ждет, пока не завершатся все потоки:

with ThreadList, LockList do
 while Count > 0 do
 begin
   UnlockList;
   Sleep(1);
 end;

Этот код тоже безопасен (по тем же соображениям) и тратит минимум процессорного времени.

в). ядро завершает свою работу - уже обычным образом и без всяких фокусов.

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


 
Семеныч   (2008-03-05 06:42) [46]

Сорьки, в п. б) фигню написал. Ждать завершения всех потоков надо так:

while True do
begin
 with ThreadList, LockList do
 try
   if Count = 0 do
     Break;
 finally
   UnlockList;
 end;
 Sleep(1);
end;


 
Пробегал...   (2008-03-05 08:38) [47]

ага   (05.03.08 4:00) [44]
Да, загрузит. А чем загрузит-то?


бесконечным вычислением значения количества потоков

ага   (05.03.08 4:00) [44]
Это будет самая быстрая реакция на выход всех потков из твово ядра


ну конечно. Быстрее специального механизма синхронизации, встроенного в windows? ;)

К тому же, забрав на себя полностью ресурсы процессора этот поток нефигово притормозит другие потоки.

ага   (05.03.08 4:00) [44]
Приоритеты?  А ты че, сам не знаешь какие у тя приоритеты потоков?


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

ага   (05.03.08 4:00) [44]
Разные - не используй Sleep(0).


я и не использую ;)

ага   (05.03.08 4:00) [44]
Ага, будет, как же. Расскажи эт своей бабушке. Ладно, я с утра добрый.
Вот у тя поток T1 вызвал ThreadSafeEnter. Выполняется:
coreCriticalSection.Enter ;
try
 if CoreFlagRun then
 begin
Тут его прерывает система и начинает работать поток T2, котрый вызыват деструктор ядра:
> Ну и в деструкторе просто выставляется флаг, после чего
> ожидается наступление события coreEvent
CoreFlagRun:= false;
WaitForSingleObject(coreEvent...
В каком состоянии будет coreEvent? Догадайся с трех раз


эх... ну ладно, я не привел код деструктора, думал это очевидно. Код на самом деле такой:

coreCriticalSection.Enter ;
try
 CoreFlagRun := false ;
finally
 coreCriticalSection.Leave ;
end;
WaitForSingleObject(coreEvent, INFINITE) ;


теперь подумай и догадывайся хоть с 10 раз.

ага   (05.03.08 4:00) [44]
А то что попадешь - к бабкам не ходи


забавно. Придумать самому хреновый код и на этом основании уверять, что попадешь в дыру ;) Ну если как ты написал - действительно, попадешь ;)


 
Пробегал...   (2008-03-05 08:49) [48]

Семеныч   (05.03.08 6:30) [45]

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

У тебя основа на том, что никто не будет дергать функции ядра, когда все потоки умрут. А ядро не завершится, пока все потоки не умрут. У меня немного не так, к сожалению ;(

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

LocalCore := Core ;
Core := nil ;
FreeAndNil(LocalCore);


при этом у экспортных функций есть проверка аля:

function lalala: boolean; stdcall;
begin
 if Assigned(Core) then
   Result := Core.nanana
 else
   Result := false ;
end;


но все равно... оставлю свой вариант на всякий случай ;)


 
Игорь Шевченко ©   (2008-03-05 10:17) [49]

Вот как же винда, зараза, объекты удаляет, когда в них больше нужды нету...Откуда она знает, не иначе, внутри шаман сидит. И ей пофиг, винде, на каких языках код потоков написан. И критических секций с семафорами она не использует, потому как накладно и рекурсия.
Но работает ведь. Зараза.


 
Пробегал...   (2008-03-05 11:52) [50]

Игорь Шевченко ©   (05.03.08 10:17) [49]
Вот как же винда, зараза, объекты удаляет, когда в них больше нужды нету..


я думаю она работает по принципу описанным Семеныч в [45]. То есть, ядро системы работает всегда, пока существуют какие-то процессы. Процессы существуют пока существуют потоки.

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

Я думаю так.

Но я уже писал, что мне это не совсем подходит... Хотя конечно можно и так делать, в любом случае так или иначе убивать все потоки имеющие доступ к экземпляру ядра, а остальные варианты вызовов методы ядра исключить...


 
Игорь Шевченко ©   (2008-03-05 12:08) [51]

Пробегал...   (05.03.08 11:52) [50]

"Вошел поток в метод - на счетчике плюс 1, вышел из метода - минус один. Соответственно, при уничтожении ядра делаю так: запрещаю новые вхождения в методы ядра, после чего ожидаю когда счетчик опуститься в ноль - это означает, что все потоки отработали на ядре. Вроде бы логично."

Вот-вот. Винда тоже самое делает с объектами, пока кто-то их использует - хрен они удалятся из системы. Это к [49]


 
Пробегал...   (2008-03-05 13:07) [52]

Игорь Шевченко ©   (05.03.08 12:08) [51]

вы к тому, что InterlockedIncreament работает куда быстрее, чем аля:

cs.Enter
try
 inc(Counter);
finally
 cs.Leave;
end;


?


 
Игорь Шевченко ©   (2008-03-05 13:08) [53]


> вы к тому, что InterlockedIncreament работает куда быстрее,
>  чем аля:


не то слово


 
Пробегал...   (2008-03-05 13:11) [54]

просто смысла нет использовать в моем варианте. У меня ожидания по WaitForSingleObject от события. Событие все равно должно выставляться в критической секции. А тогда уже не имеет смысл использовать Interlocked.

Это надо переделать логику - точно убивать все потоки и не допускать возможности новых вызовов методов ядра. Тогда да, от WaitForSingleObject и от события можно уйти, а для счетчика использовать Interlocked...


 
Игорь Шевченко ©   (2008-03-05 13:16) [55]

[31]


 
Пробегал...   (2008-03-06 02:16) [56]

ну ладно, вот и поговорили ;)


 
имя   (2008-03-06 03:40) [57]

Удалено модератором


 
Пробегал...   (2008-03-06 19:13) [58]

а что лучше использовать для синхронизации доступа к потокам - критические секции или эвенты? У меня отложилось в памяти, что критич. секции самое быстрое из средств синхронизации. По крайней мере в некоторых случаях, хотя в других они могут приводить к созданию мьютекса (вроде Рихтер, не помню почему).

То есть, крит. секция с ее Enter / Leave, или WaitForSingleObject на событии лучше? Когда первый вошедший поток сбрасывает событие, а после завершения работы выставляет.


 
Palladin ©   (2008-03-06 19:40) [59]

Да можешь и события использовать... они идейно не для этого сделаны, да и к тому"же нужно иметь ввиду что один поток может два раза сделать Enter в одну и туже критическую секцию, а вот два раза "войти" в событие у тебя не получится :)


 
Пробегал...   (2008-03-06 20:03) [60]

Palladin ©   (06.03.08 19:40) [59]
Да можешь и события использовать


да я понимаю что могу. Просто интересно стало что быстрее работает.


 
ЦУП ©   (2008-03-06 20:10) [61]


> Пробегал...   (06.03.08 20:03) [60]
> Palladin ©   (06.03.08 19:40) [59]
> Да можешь и события использовать
>
> да я понимаю что могу. Просто интересно стало что быстрее
> работает.


Быстрее любые другие объекты кроме  критических секций.
На них расходуется в сотни раз больше тактов процессора.


 
Пробегал...   (2008-03-06 20:24) [62]

ЦУП ©   (06.03.08 20:10) [61]

любые другие объекты синхронизации? Ничего не понял, ты хочешь сказать, что критические секции самый медленынй способ синхронизации?! Медленне мьютекса, семафоров и прочее?!?! Я был совершенно другого мнения


 
ЦУП ©   (2008-03-06 20:31) [63]


> Пробегал...   (06.03.08 20:24) [62]
> ЦУП ©   (06.03.08 20:10) [61]
>
> любые другие объекты синхронизации? Ничего не понял, ты
> хочешь сказать, что критические секции самый медленынй способ
> синхронизации?! Медленне мьютекса, семафоров и прочее?!?
> ! Я был совершенно другого мнения


Точно! Сейчас аргументированно  не могу доказать, но после изучения в прошлом этой проблемы отложилось именно это.


 
Loginov Dmitry ©   (2008-03-06 21:03) [64]

> Быстрее любые другие объекты кроме  критических секций.


У Рихтера есть объяснение механизма работы критической секции. Из него следует, это это - самый быстрый способ блокировки.


 
Пробегал...   (2008-03-06 22:03) [65]

таки перечитал Рихтера ;)

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

WaitForSingleObject делает то тоже самое, видимо. Поэтому с этой точки зрения пофигу практически.

Просто притические секции могут применять спин. блокировку, то есть проверку-sleep-проверку-sleep, и так заданное количество раз, только после этого уже переход в режим ядра и заморозка потока. Кстати, с этой точки зрения судя по всему Suspend и Resume тоже не самые быстрые операции.

Но установку количества проверок критической секцией условий блокировки перед уходом в режим ядра делается функцией http://msdn2.microsoft.com/en-us/library/ms686197.aspx - а она введена только начиная с w2k. И пригодна только для многопроцессорных машин.

А так видимо разницы нет.


 
Пробегал...   (2008-03-06 22:08) [66]

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

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


 
Пробегал...   (2008-03-06 22:10) [67]

ЦУП ©   (06.03.08 20:31) [63]
Точно! Сейчас аргументированно  не могу доказать, но после изучения в прошлом этой проблемы отложилось именно это


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

Исключение - это та самая спин-блокировка на многопроцессорной тачке, там критическая секция может отработать быстрее.


 
Пробегал2...   (2008-03-09 03:50) [68]

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

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


 
Пробегал2...   (2008-03-09 04:34) [69]

"Вошел поток в метод - на счетчике плюс 1, вышел из метода - минус один. Соответственно, при уничтожении ядра делаю так: запрещаю новые вхождения в методы ядра, после чего ожидаю когда счетчик опуститься в ноль - это означает, что все потоки отработали на ядре. Вроде бы логично."

Вот-вот. Винда тоже самое делает с объектами


Хотя это ИШ ссылался на мои слова, все равно это ненадежный алгоритм работы. Итак, идет финализация ядра.

Допустим, мы запретили новые вхождение в методы ядра.
В каждом методе ядра при входе идет InterlockedIncreament счетчика, по выходу из метода - InterLockedDecreament.

В финализации ядра мы типа ждем когда счетчик станет равным нулю.

Но тут есть и минусы, и баги:

1) некий поток может войти в метод ядра, но не успет вызвать InterlockedInreament. Тогда в финале ядра счетчик будет равен 0, ядро подумает что потоков внутри него не осталось и финализация пойдет дальще. А в это время тот неуспевший поток сделает InterlockedIncreament и продолжив работу, приведя к непредсказуемым последствиям.

2) каким образом контролировать, что счетчик в ноль ушел? Аля:

while Counter <> 0 do sleep(1);

? Не сильно ли это загрузит процессор, мешая другим потоком работать (в том числе совершать последние финальные действия).



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

Форум: "Прочее";
Текущий архив: 2008.04.20;
Скачать: [xml.tar.bz2];

Наверх




Память: 0.68 MB
Время: 0.05 c
4-1178810111
йцукенг
2007-05-10 19:15
2008.04.20
Как получить handle элемента управления окна?


3-1195787083
magistr_yoda
2007-11-23 06:04
2008.04.20
Какую БД и компоненты использовать, для работы без BDE


2-1206733636
Wold
2008-03-28 22:47
2008.04.20
TMainMenu + OnDrawItem


9-1169026485
basf
2007-01-17 12:34
2008.04.20
вектора нормалей


15-1204701267
sds
2008-03-05 10:14
2008.04.20
Вопрос по MS SQL 2000





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
Английский Французский Немецкий Итальянский Португальский Русский Испанский