-
В своем проекте натолкнулся на глюк, долго локализовал ошибку. В результате написал примерчик, который у меня также... эээ... работает не так, как я ожидаю. Вот пример:
type
TForm1 = class(TForm)
Button1: TButton;
Memo1: TMemo;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
public
end;
TTestThread = class(TThread)
protected
procedure Execute; override;
end;
var
Form1: TForm1;
TestList: TThreadList ;
implementation
procedure TForm1.FormCreate(Sender: TObject);
begin
TestList := TThreadList.Create ;
TTestThread.Create(false);
end;
procedure TTestThread.Execute;
begin
while true do
begin
TestList.LockList ;
sleep(200);
TestList.UnlockList ;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Memo1.Lines.Add('Перед: '+IntToStr(GetTickCount));
TestList.LockList ;
Memo1.Lines.Add('После: '+IntToStr(GetTickCount));
TestList.UnlockList ;
end;
Смысл в чем - некий поток занимает критическую секцию, далее работает, затем выходит из критической секции (чтобы дать возможность другим потокам войти), далее опять в нее входит, работает и так по кругу. При этом изредка другой поток тоже пытается войти в эту секцию. Оба они войти очевидно не могут, но я думал что если второй завис над входом, то он войдет после того как первый освободит эту секцию. Но нифига! У меня в системе второй поток умудряется висеть по десятку секунд! В то время как первый поток за это время десятки раз покидает и заново входит в критическую секцию. Конечно, это в общем можно объяснить, навроде второй поток засыпает, а первый поток как только выходит тут же входит опять, второй поток даже не успевает проснуться. Вроде логично, но ТАААКОЙ таймаут меня удивляет все таки. Я думал, коли поток уж несколько секунд ждет критич. секции, то другой поток не сможет в это время десятки раз входить и выходить из секции. В связи с этим: 1) реально и нормально ли такое поведение в теории? 2) проверьте кто может на своей системе какие будут результаты? Розыч, например, слабо верит что такое является нормальным и у него на XP такой блокировки никогда не происходит. А у меня на Vista каждый раз! Вот мои результаты: Перед: 31541686
После: 31549049
Перед: 31769135
После: 31786467 Видно, что хотя у активного потока sleep стоит всего 200 миллисекунд, но основной поток "пробивался" в первом случае 8 секунд (!!!), и во втором случае аж 17 секунд!!! за это время активный поток по идее где-то раз 90 вошел и вышел из этой критической секции. Код теста выше. Как тестировать - запускаете и жмете несколько раз на кнопку. Выкладываете результаты ;) Желательно с пояснением какая у вас ОС и железо. У меня Vista home premium SP1 сборка 6001 Процессор Intel E4400 2ГГц 3) как бороться с такой фичей? Хотелось бы более менее "равного" распределения доступа к критической секции между потоками.
-
не тестировал, но бороться можно добавив такой код while true do
begin
Sleep(0); TestList.LockList ;
sleep(200);
TestList.UnlockList ;
end;
-
И спасибо кстати всем тем, кто придумал эту гениальную защиту по борьбе с реферальными ссылками. Молодцы просто, красавцы даже!
В результате невозможно дать ссылку ни на какой ресурс файлообменный, где я выложил EXE шник теста. Как всегда свое понимание удобства.
-
> Добежал (22.10.08 21:17)
Процессор какой?
-
> oxffff © (22.10.08 22:37) [3]
скорее всего Intel E4400 2ГГц
-
> скорее всего Intel E4400 2ГГц
Поведение очень странное, второму потоку "больше везет в захвате". Возможно поможет
1. SetCriticalSectionSpinCount
2. Все же код выше "не совсем честный"
Вместо
procedure TTestThread.Execute; begin while true do begin TestList.LockList ; sleep(200); <- другой поток уже успевает отработать. TestList.UnlockList ; end; end;
Поставить
procedure TTestThread.Execute; begin while true do begin TestList.LockList ; TestList.UnlockList ; sleep(200); end; end;
-
> sleep(200);
Это слишком большое значение поскольку от самого освобождения до еще одного захвата проходит совсем ничего. Освободили, и тут же захватили.
-
> у него на XP такой блокировки никогда не происходит.
Здесь видимо дело в планировщике.
-
> <- другой поток уже успевает отработать.
Не, это не правда. :)
Получается при TestList.UnlockList - т.е. при сигнализировании события в XP есть запланированные ожидающие потоки, а в Vista ожидающие потоки не запланированы.
-
Ну вот и ответ. http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=510229&SiteID=1I was reading the article at http://msdn.microsoft.com/windowsvista/default.aspx?pull=/library/en-us/dnlong/html/AppComp.asp and noticed the section about critical sections. There is a bullet point that says the following. " Should prevent starvation. Applications that call Sleep while holding the critical section lock now can cause starvation for other threads that need the lock. Sleep calls should be placed after the LeaveCriticalSection call. " Critical sections are no longer acquired in FIFO order, this since Windows2003 SP1. This means that on W2K3 and higher, in your sample, the same thread will stay running after leaving the critical section , while on XP or lower, the thread will pre-empt and the next waiting thread in FIFO order will get scheduled. This is done to prevent performnce degradation due to excessive context switching.
-
> Это слишком большое значение поскольку от самого освобождения > до еще одного захвата проходит совсем ничего. Освободили, > и тут же захватили.
так в том и дело - это повторение ситуации в проекте. Там тоже самое, один поток работает активно, но периодически освобождает критическую секцию, чтобы другие потоки если надо смогли ее захватить и поработать со списком. Поэтому он покидает секцию и тут же в нее входит.
В XP все было гуд, по очереди получали когда нужно и проблем не было. А вот в vista такие блин новвоведения... Ужасно, первый поток до 50 секунд может не получать доступа к критической секции ;(
SetCriticalSectionSpinCount вроде не работает на 9x... Не хотелось бы.
Может использовать для синхронизации мьютексы? В них то нет надеюсь таких усовершенствований...
А вообще очень неожиданно, надо теперь иметь в виду, у меня много где так критические секции используются...
-
Ошибка в том, что есть необходимость в таком коде. Искать по кейвордам "Synchronize" && "Архангельский" :)
А я буду иметь ввиду, что виндовая КС до Vista неэффективна.
-
Вопрос - что делается с TestList в каких потоках ? Возможно, тут лучше всего будет перейти на неблокирующий алгоритм.
-
2Автор. Спасибо за тему. Интересно. Насколько я понимаю sleep(0) должен провоцировать смену активного потока. делать, действительно, нужно после покадания крит. секции.
Насчет мьютексов. Я думаю, тут опыт даст ответ. Попробуй. Расскажи нам. Лично мне оч. интересно.
-
2gauv Что к алгоритму придираться. Причина то не в этом в любом случае. Причину Антонов Сергей вроде нашел.
-
> [14] тимохов (23.10.08 12:50) > Причину Антонов Сергей вроде нашел.
За что ему кстати спасибо.
Получается, что КС до 2003 исправляла ошибки неправильного использования КС, при этом тормозила правильное.
Недавно пытался обсуждать на RSDN использование альтернативной КС (boost::mutex), тогда мнения разделились, хотя мне альтернативная КС показалась лучше (в т.ч. и по производительности). Эта ветка - ещё один аргумент против стандартной КС.
-
> Ошибка в том, что есть необходимость в таком коде
о, да! Я тоже так считаю, что все ошибки только от того, что приходится писать программы.
> Вопрос - что делается с TestList в каких потоках ?
ну представь что в списке хранится информация об устройствах. Один поток берет по очереди из списка информацию об устройствах и опрашивает их. И так по кругу пока его не остановят.
А второму потоку иногда нужно узнать данные (и текущее состояние) какого-нибудь устройства или добавить / удалить устройство, это происходит редко.
Само сканирование устройства сделано внутри критической секции, то есть поток выбирает устройство, сканирует его, и заносит информацию новую об устройстве обратно в список. Потом выходит из секции, заходит в нее заново, выбирает следующее устройство и процесс повторяется.
Сканирование устройства происходит внутри секции. Я пошел на это сознательно, ибо иначе придется выходить из секции, сканировать устройство, потом опять его искать в списке и заносить информацию. Устройств может быть относительно много, чтобы не тратить время на постоянный поиск устройства внутри списка (ведь во время сканирования устройства список устройств тогда может быть изменен).
Впрочем, я придумал как изменю ситуацию. Ведь можно просто запомнить позицию устройства в списке. Потом по входу в критич. секцию просто проверить тоже ли самое устройство находится на этом же месте, если да - то обновить. Будет работать с учетом того, что устройства редко добавляются / изменяются.
Но вообще для меня все это стало неожиданностью. Я знаю что сканирование не может занимать более 50мс, поэтому осознанно шел на то, думал в критич. секции потоки встают в очередь друг за другом, ибо при ожидании насколько я знаю для критической секции создается мьютекс. Так говорит Рихтер, наверное в Vista решили прооптимизировать. Цель оптимизация понятна, но тем не менее ;)
-
> [16] Добежал (23.10.08 13:23) > Я тоже так считаю, что все ошибки только от того, что приходится > писать программы.
Не передёргивай. У тебя ошибка, неправильная синхронизация. > Само сканирование устройства сделано внутри критической > секции, то есть поток выбирает устройство, сканирует его, > и заносит информацию новую об устройстве обратно в список. > Потом выходит из секции, заходит в нее заново, выбирает > следующее устройство и процесс повторяется.
Должно хватить этого http://msdn.microsoft.com/en-us/library/ms686962(VS.85).aspx
-
Когда КС используется правильно, на ней вообще блокировки редко происходят. Именно за счёт этого она и легче события, семафора или мьютекса.
-
> У тебя ошибка, неправильная синхронизация
и в чем она неправильная? > Должно хватить этого > http://msdn.microsoft.com/en-us/library/ms686962(VS.85). > aspx
Не должно. Я везде использую дельфовые TThreadList - мне так удобнее. Я рад, что MS начинает делать это на уровне системы, но хотя бы: InterlockedPushEntrySList Function: Client Requires Windows Vista or Windows XP. Даже в w2k не поддерживается. > Когда КС используется правильно, на ней вообще блокировки > редко происходят
совершенно верно. У меня все точно также. Поток работает себе и работает, изменение списка происходит крайне редко.. За день может и ни разу не наступить. То есть, за целый день ни одной блокировки. Это можно считать редко? Но зато когда она наступает эта блокировка - повиснуть все может нафиг секунд на 30. И это очень плохо.
|