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

Вниз

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

 
Пробегал...   (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

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



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

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

Наверх




Память: 0.59 MB
Время: 0.021 c
2-1206386451
unknowing
2008-03-24 22:20
2008.04.20
Дескриптор Com-порта


15-1204548079
Dmitry S
2008-03-03 15:41
2008.04.20
один DCHP в двух сетях


2-1206508766
@!!ex
2008-03-26 08:19
2008.04.20
Определить, что текущая раскладка Ru или не Ru


2-1206555595
Strate
2008-03-26 21:19
2008.04.20
Сервис. Не позволить пользователю завершить.


2-1206554828
redlord
2008-03-26 21:07
2008.04.20
блокировка вставки текста в tedit