Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[44.222.242.27] |
|
Сообщ.
#1
,
|
|
|
Бесконечный цикл со Sleep(1) почти не грузит процессор.
Что там такого зашито внутри? Как это можно реализовать кроме как через Sleep? |
Сообщ.
#2
,
|
|
|
Цитата Jin X @ Довольно логично: задача Sleep'а - указать ОС, что "такой-то процесс не работает, можете заняться своими делами", что она и делает.Бесконечный цикл со Sleep(1) почти не грузит процессор. Цитата Jin X @ Уточните, что подразумевается под словом 'это'? Если "загрузку ЦП", то можно банально в цикле считать x=cos(x) и загрузка будет выше крыши (но одно ядро, конечно). (Тривиальное распараллеливание загрузит все ядра). Как это можно реализовать кроме как через Sleep? |
Сообщ.
#3
,
|
|
|
Цитата Jin X @ Бесконечный цикл со Sleep(1) почти не грузит процессор. Что там такого зашито внутри? Во-первых, зашито само название функции - Sleep, т.е. поток "засыпает" (приостанавливается) на время >= указанного значения. Во-вторых, "зашита" специфика отсчета времени в ОС - счет ведется дискретно по прерываниям системного таймера со стандартным (дефолтным) интервалом в 1/64 сек = 15.625 мс. Отсюда и существенная разница Sleep со значением 0 и > 0. В любом случае вызов Sleep передает управление ОС, которая должна запустить следующий по очереди "простаивающий и готовый к исполнению" поток на данном процессоре. Но если система не загружена и таковых потоков нет, то при Sleep(0) управление практически сразу возвращается вызвавшему эту функцию потоку. А в случае Sleep(X > 0) ОС обязана выдержать интервал времени не менее заданного, но поскольку счет времени ведется дискретно, то возобновление исполнения потока происходит только по очередному тику сис.таймера, т.е. с задержкой от 0 до 15.625 мс при первом или однократном вызове Sleep и с задержкой, близкой к 15 мс при вызове Sleep в "пустом\холостом" цикле (за счет авто-синхронизации с сис. таймером). В итоге получается, что при вызове в холостом цикле Sleep(1) практически эквивалентна Sleep(15). Вот и прикинь загрузку процессора - несколько "жалких" сотен (или даже тысяч - роли не играет) тактов на вызов Sleep с переходом в ядро и ~15 мс*(частота_процессора) тактов чистого сна. Ес-но, что "Бесконечный цикл со Sleep(1) почти не грузит процессор" |
Сообщ.
#4
,
|
|
|
Цитата Jin X @ Что там такого зашито внутри? event = CreateEvent(NULL, FALSE, FALSE, NULL); //... void Sleep(DWORD timeot) { WaitForSingleObject(event, timeout); } Добавлено На самом деле функция делает ничто иное как переводит поток в очередь спящих потоков с таймаутом пробуждения и переключает поток. Не грузит она потому что во время таймаута работают другие потоки. Добавлено Цитата leo @ В итоге получается, что при вызове в холостом цикле Sleep(1) практически эквивалентна Sleep(15). Я конечно не знаю как там в виндах реализовано, но я делал пробуждалку не только по таймеру. Добавлено Цитата Jin X @ Как это можно реализовать кроме как через Sleep? А зачем оно надо? |
Сообщ.
#5
,
|
|
|
Цитата Славян @ Наоборот разгрузку. Мне нужно сделать цикл (spin-lock, в частности), который будут грузить проц минимально.Уточните, что подразумевается под словом 'это'? Полагаю, что Sleep(1) - это не самый оптимальный способ сделать это. |
Сообщ.
#6
,
|
|
|
Вот ещё такой вариант нашёлся:
Цитата \win2k\private\windows\media\avi\drawdib.16\ddt\ddt.c Он выглядит несколько лучше, ибо всё приложение не "виснет" во время такой спячки, а хоть как-то реагирует. void Sleep(UINT uSleep) { MSG msg; int id; id = SetTimer(NULL, 42, uSleep, NULL); while (GetMessage(&msg, NULL, 0, 0)) { if (msg.message == WM_TIMER && msg.wParam == (WORD)id) break; TranslateMessage(&msg); DispatchMessage(&msg); } KillTimer(NULL, id); } |
Сообщ.
#7
,
|
|
|
Ок. Чем же можно разгрузить проц на очень короткий срок (мкс, грубо говоря), кроме Sleep'а?
Добавлено К сожалению, pause не разгружает проц вообще никак |
Сообщ.
#8
,
|
|
|
Цитата Jin X @ К сожалению, pause не разгружает проц вообще никак Разгружает, но не передает управление остальным потокам, так что как минимум стоит вставить еще и SwitchToThread. Загрузка проца в Винде имеет лишь посредственное отношение к фактической загрузке, а конкретно она высчитывается из фактического времени работы потока. Т. е. если в системе все потоки кроме твоего спят, а твой занят хоть чем-то кроме сна, то загрузка и будет 100%. Мне кажется в твоем случае стоит задуматься над событийной (асинхронной) моделью программы. Т. е. вместо твоего пустого цикла использовать WaitForSingleObject для ожидания результата чего-то. Тогда проблемы с пустой нагрузкой отпадут сами собой. Добавлено И да, если поток gui'шный, то можно использовать обработку сообщений потока через PostThreadMessage. |
Сообщ.
#9
,
|
|
|
Цитата shm @ А как я могу ждать обнуления 32-битной переменной через WaitForSingleObject?Разгружает, но не передает управление остальным потокам, так что как минимум стоит вставить еще и SwitchToThread. Загрузка проца в Винде имеет лишь посредственное отношение к фактической загрузке, а конкретно она высчитывается из фактического времени работы потока. Т. е. если в системе все потоки кроме твоего спят, а твой занят хоть чем-то кроме сна, то загрузка и будет 100%. Мне кажется в твоем случае стоит задуматься над событийной (асинхронной) моделью программы. Т. е. вместо твоего пустого цикла использовать WaitForSingleObject для ожидания результата чего-то. Тогда проблемы с пустой нагрузкой отпадут сами собой. Я запускал несколько потоков @: pause jmp @ и смотрел на температуру процессоров. Так вот, наличие pause никак не неё не влияет. Так что, у меня с разгрузкой всё-таки вопрос... На данный момент решил сделать spin-loop-циклы вот так: ( ( 265 spin-циклов, затем SwitchToThread ) * 4 раза, затем Sleep(1) ) * 4 раза. Итого выходит 4096 циклов. Не так много, но и не мало. После этого переходим на Sleep(1) после каждой проверки, максимально разгружая таким образом проц. Готов обсудить цифры 256, 4, 4 на предмет оптимальности |
Сообщ.
#10
,
|
|
|
Цитата shm @ В продолжение темы: каким образом ядро делает так, что температура процессора остаётся низкой? А любой поток, делая даже pause в цикле будет нагревать его? На ум приходит только hlt, но...? Загрузка проца в Винде имеет лишь посредственное отношение к фактической загрузке |
Сообщ.
#11
,
|
|
|
Цитата Jin X @ А как я могу ждать обнуления 32-битной переменной через WaitForSingleObject? Явно никак. Но ты можешь в том месте, где ее устаналиаешь, добавить еще SetEvent. И все будет ОК. Добавлено Цитата Jin X @ Так вот, наличие pause никак не неё не влияет. Довольно странно. Видимо в твоем процессоре такая реализация. Цитата Jin X @ ( ( 265 spin-циклов, затем SwitchToThread ) * 4 раза, затем Sleep(1) ) * 4 раза. Итого выходит 4096 циклов. Не так много, но и не мало. После этого переходим на Sleep(1) после каждой проверки, максимально разгружая таким образом проц. Криво и косо. Цитата Jin X @ В продолжение темы: каким образом ядро делает так, что температура процессора остаётся низкой? В системе есть System Idle потоки на каждое ядро. Они выполняют либо hlt, либо специальный aml код из acpi (и то и другое недоступно для user mode). |
Сообщ.
#12
,
|
|
|
Цитата shm @ Я проверил это на 2-х процессорах: у меня на i5 (декстоп), на другом i5 (ноутбук) (позже ещё на i3 проверю, на другом ноуте). Результат один. Я даже больше скажу: с pause чуть больше греет Довольно странно. Видимо в твоем процессоре такая реализация. Цитата shm @ В чём кривизна? Как тогда не криво и не косо сделать?Криво и косо. Сделать через ивенты? (Но по любому же первые N (например, 4096) циклов надо будет оставить) |
Сообщ.
#13
,
|
|
|
Цитата Jin X @ Я даже больше скажу: с pause чуть больше греет Будет время - я у себя протестирую. Цитата Jin X @ В чём кривизна? В том, что ты вместо использования готовых средств синхронизации занимаешься их эмуляцией. Цитата Jin X @ (Но по любому же первые N (например, 4096) циклов надо будет оставить) if N < 4094 then ... else WaitFor... |
Сообщ.
#14
,
|
|
|
Цитата shm @ Изначально была идея просто сделать spin-lock, но с экономией ресурсов, если процесс затянется. С использованием 1 простой переменной типа DWord или Int64, без Initialize и Delete. Т.е. наиболее быстрый и с наименьшими заморочками.В том, что ты вместо использования готовых средств синхронизации занимаешься их эмуляцией. Сдаётся мне, что установка/ожидание event'а процесс тяжёлый... Хотя, могу ошибаться. |
Сообщ.
#15
,
|
|
|
Я что скажу: если у тебя spin-локи грузят проц, то значит нужны эвенты или иные средства синхронизации (ибо цикл синхронизации отнюдь не маленький), которые могут усыпить поток. Во всяком случае что это процесс не тяжелее, чем sleep.
|
Сообщ.
#16
,
|
|
|
Synchronization Functions
Critical section Slim Reader/Writer (SRW) и возможно One-time initialization Synchronization barrier |
Сообщ.
#17
,
|
|
|
Цитата Jin X @ Сдаётся мне, что установка/ожидание event'а процесс тяжёлый... Хотя, могу ошибаться. Цитата shm @ Реально...Во всяком случае что это процесс не тяжелее, чем sleep. Замерил скорость выполнения разного кода (в тактах), получилось вот что: Холостой ход - 30-60 cmpxchg (10 раз) - 60-120 lock cmpxchg (10 раз) - 400-550 SpinLoop 1024 циклов (cmp) - 26000-36000 SpinLoop 1024 циклов (locked cmpxchg) - 50000-70000 CreateEvent - 30000-50000 Set/ResetEvent - 2000-4000 CloseHandle - 4000-7000 SwitchToThread - 2500-4000 Sleep(0) - 3000-5000 Sleep(1) - 160 тыс-3 млн WaitForSingleObject (signaled) - 2000-5000 InitializeCriticalSectionAndSpinCount - 1800-2800 InitializeCriticalSection - 1400-2400 EnterCriticalSection (свободная) - 80-150 EnterCriticalSection (рекурсивно) - 100-300 DeleteCriticalSection - 500-1500 Добавлено Shaggy, гляну, thanx! |
Сообщ.
#18
,
|
|
|
Вопрос: есть ли гарантия, что WaitForSingleObject успеет сработать, если другой поток сделает SetEvent и CloseHandle подряд?
|
Сообщ.
#19
,
|
|
|
Цитата Jin X @ CreateEvent хоть и долгая тема Его нужно дернуть 1 раз при запуске и все. Цитата Jin X @ есть ли гарантия, что WaitForSingleObject успеет сработать, если другой поток сделает SetEvent и CloseHandle подряд? А зачем так делать? Нет, нету гарантий. Если уж так надо, то после WaitForSingleObject и закрывай. |
Сообщ.
#20
,
|
|
|
Цитата shm @ Если сделать инициализацию 1 раз, значит нужно делать и удаление (типа DeleteCriticalSection), а я хочу сделать только Lock (Enter) и Unlock (Leave). Есть прикольные функции: WaitOnAddress и WakeByAddressSingle/WakeByAddressAll, жаль только, что существуют они в Windows 8+ (посему придётся комбинировать их и Event'ы для поддержки работы в Windows 7/XP). Его нужно дернуть 1 раз при запуске и все. |
Сообщ.
#21
,
|
|
|
Jin X
А свою написать? Мне не понравилось то что для критической секции нужна глобальная переменная. И я сделал вой вариант. procedure TaskSwitch; var Cis:PCriticalSection; Status:Boolean; begin Status:=GetIntStatus; DisableInt; CisBegin(Cis); if TaskSetModeCmd.Lock=MUnLocked then ExecuteCMD; AlgTaskSwitch; CisEnd(Cis); if Status=False then EnableInt; end; Прикреплённый файлUSynchronization.pas (9,15 Кбайт, скачиваний: 316) Добавлено PS. Код сырой и не тестированный. Просто как пример. |
Сообщ.
#22
,
|
|
|
Цитата Jin X @ Если сделать инициализацию 1 раз, значит нужно делать и удаление (типа DeleteCriticalSection), а я хочу сделать только Lock (Enter) и Unlock (Leave) Это неэффективно. Неужели так лень пару лишних функций вызвать? Pavia, Я не претендую на правильность, но все же. class CriticalSection: private Event { static void EnterSingle(CriticalSection *cs) { //std::cout << "EnterSingle" << std::endl; } static void LeaveSingle(CriticalSection *cs) { //std::cout << "LeaveSingle" << std::endl; } static void EnterMultitasking(CriticalSection *cs); static void LeaveMultitasking(CriticalSection *cs); static void EnterSmp(CriticalSection *cs); static void LeaveSmp(CriticalSection *cs); static void (*EnterProc)(CriticalSection *cs); static void (*LeaveProc)(CriticalSection *cs); volatile atom State; // atom LockCounter; //locked bool OnWaitBegin(); //bool OnSetStateSingle(Thread *); public: class Lock { CriticalSection *section; public: Lock(CriticalSection &cs) { section = &cs; cs.enter(); } ~Lock() { section->leave(); } }; CriticalSection(); CriticalSection(bool Busy); static void SetMutlitaskingMode() { EnterProc = &EnterMultitasking; LeaveProc = &LeaveMultitasking; } static void SetSmpMode() { EnterProc = &EnterSmp; LeaveProc = &LeaveSmp; } void enter() { EnterProc(this); } void leave() { LeaveProc(this); } bool IsLock() const { return (State != 0); } bool enter(DWORD Timeout); }; //... void CriticalSection::EnterSmp(CriticalSection *cs) { static const int max_spin_it = 1024; unsigned int cpu_id, result; int i; cpu_id = GetCurrentCpuId() + 1; result = CpuCompareAndSwapLock<volatile atom>(&cs->State, 0, cpu_id); if(result == 0) return; else if(result != cpu_id) { for(i = 0; i < max_spin_it; i++) { if(cs->State == 0) { cpu_id = GetCurrentCpuId() + 1; result = CpuCompareAndSwapLock<volatile atom>(&cs->State, 0, cpu_id); if(result == 0) return; } CpuPause(); } } cs->Wait(); cs->State = GetCurrentCpuId() + 1; } void CriticalSection::LeaveSmp(CriticalSection *cs) { if(cs->HaveWaitingThreads()) cs->SetState(); else cs->State = 0; } |
Сообщ.
#23
,
|
|
|
Pavia, what's that?
uses SysSupport, UMemoryManager, UMemoryMap; Добавлено Цитата Pavia @ Что именно написать своё?А свою написать? shm, то ли неполный код, то ли... х/з. GetCurrentCpuId - ? CpuCompareAndSwapLock - ? Wait - ? SetState - ? HaveWaitingThreads - ? ... |
Сообщ.
#24
,
|
|
|
shm
Цитата shm @ Pavia, Я хотел показать метод с патчингом. В стеке страниц выделяем страницы. Страницы связаны в цепочку двойным списком. В этих страницах хранятся адреса критических секций и её данные как то мьютекс. При 1 заходе в критическую секцию адрес патчится на спинлок. Для задачи Jin X: Цитата Jin X @ Если сделать инициализацию 1 раз, значит нужно делать и удаление (типа DeleteCriticalSection), а я хочу сделать только Lock (Enter) и Unlock (Leave). Можно сделать по аналогии патч который накладывает 5-байтовый NOP. |
Сообщ.
#25
,
|
|
|
Цитата Jin X @ shm, то ли неполный код, то ли... х/з. Это для Pavia. Код не имеет никакого отношения к Win, как и код Pavia. Да простят нас админы за оффтоп. Добавлено Pavia, я у тебя не вижу усыпления потока. Без этого твои мьютексы это скорее спинлок, что крайне неэффективно в силу очевидных причин. Связанный список я у себя храню прямо в структуре потока, которую очень легко достать средствами TLS. |
Сообщ.
#26
,
|
|
|
Цитата Jin X @ Замерил скорость выполнения разного кода (в тактах), получилось вот что: lock cmpxchg (10 раз) - 400-550 pinLoop 1024 циклов (locked cmpxchg) - 50000-70000 Это для случая, когда только один поток крутит спин-луп. Если же его будут крутить два и более потока одновременно, то эти цифирьки могут увеличиться в несколько раз (где-то от 3 до 5). Дело в том, что lock cmpxchg в любом случае производит запись в переменную (записывается либо новое, либо старое значение), поэтому соответствующая линейка кэша оказывается модифицированной, со всеми вытекающими последствиями синхронизации кэшей 1-2 уровня при многопоточном доступе. В случае одного потока модифицированная линейка остается в кэше L1 того ядра, на котором работает поток. А в случае двух и более потоков, она постоянно перебрасывается из L1 одного ядра в L1 другого через общий L3. Поэтому, чтобы избежать этих перебросов, в приведенных тобой примерах от Intel перед lock-обменом используется простое сравнение переменной "только на чтение". В этом случае, при наличии нескольких ожидающих потоков, они до снятия лока только читают линейку, не изменяя ее, и соотв-но она находится в неизменном shared состоянии в L1 этих потоков\ядер |
Сообщ.
#27
,
|
|
|
Цитата leo @ Я даже скажу, что lock вообще сильно тормозит процесс. Особенно если это будут делать одновременно несколько потоков...Дело в том, что lock cmpxchg в любом случае производит запись в переменную (записывается либо новое, либо старое значение) Из Intel Instruction Set Reference (cmpxchg): TEMP ← DEST IF accumulator = TEMP THEN ZF ← 1; DEST ← SRC; ELSE ZF ← 0; accumulator ← TEMP; DEST ← TEMP; FI; |
Сообщ.
#28
,
|
|
|
Цитата Jin X @ Можно было бы обойтись и без неё. Как? |
Сообщ.
#29
,
|
|
|
Цитата shm @ А в чём её суть?Как? Temp := Dest Dest := Temp |
Сообщ.
#30
,
|
|
|
Быть может там прописана технология реализации. Т.е. TEMP - a'la кэш, а в конце команды обязательно надо что-то послать в память, ну а потому как совпадёт кэш с DEST, то не страшно его и послать обратно!??
|
Сообщ.
#31
,
|
|
|
Цитата Jin X @ А в чём её суть? В атомарности. Цитата Jin X @ Temp := Dest Dest := Temp Ни разу не атомарно. |
Сообщ.
#32
,
|
|
|
Цитата Jin X @ А вообще, непонятно, зачем cmpxchg делает запись во втором случае. Можно было бы обойтись и без неё. Это делается для упрощения "железной логики": Цитата Intel А избежать лишних тормозов при вызове cmpxchg в спин-цикле можно программно, добавив перед lock обычное сравнение (как рекомендует интел - “test, test-and-set” technique). To simplify the interface to the processor’s bus, the destination operand receives a write cycle without regard to the result of the comparison. ... (The processor never produces a locked read without also producing a locked write.) |
Сообщ.
#33
,
|
|
|
Цитата shm @ В атомарности. Цитата shm @ Иии?Ни разу не атомарно. Я не понимаю, что ты хочешь сказать Приведённый мной выше код - это внутренняя логика инструкции cmpxchg. leo, теперь ясно. |
Сообщ.
#34
,
|
|
|
Цитата Jin X @ Я не понимаю, что ты хочешь сказать То, что именно такая форма инструкции удобна для реализации lock-free алгоритмов и те же примитивов синхронизации. |
Сообщ.
#35
,
|
|
|
shm, ничуть не удобна. Потому что пока она будет делать Temp = Dest и Dest = Accumulator (в случае успешной проверки входа критической секции), то же самое может сделать и другой. И оба войдут в неё.
Temp1 = Dest Temp2 = Dest Temp1 = Src ? (Yes) Temp2 = Src ? (Yes) Dest = Accumulator Dest = Accumulator Добавлено Или мы о разном? |
Сообщ.
#36
,
|
|
|
Цитата Jin X @ Потому что пока она будет делать Она это делает атомарно (при наличии lock префикса и для SMP). Ну как еще написать не знаю. Добавлено Аппаратно эта инструкция, насколько я понимаю, реализована совсем не так, как ты расписал. Добавлено Я с трудом представляю как без этой инструкции можно реализовать тот же lock-free список. Если у тебя есть идеи - покажи. Кстати эта функция именно в таком виде используется в реализации практически всех примитивов синхронизации. |
Сообщ.
#37
,
|
|
|
Цитата shm @ При наличии lock - естественно. Но ты ж сам написал:Она это делает атомарно (при наличии lock префикса и для SMP). Ну как еще написать не знаю. Цитата shm @ Если же имелось в виду:То, что именно такая форма инструкции удобна для реализации lock-free алгоритмов и те же примитивов синхронизации. Цитата тогда без вопросов Неблокирующая синхронизация — подход в параллельном программировании на симметрично-многопроцессорных системах, проповедующий отказ от традиционных примитивов блокировки, таких, как семафоры, мьютексы и события. Разделение доступа между потоками идёт за счёт атомарных операций и специальных, разработанных под конкретную задачу, механизмов блокировки. © Википедия Цитата shm @ Это код из свежего Intel Instruction Reference (сообщение #27). Аппаратно эта инструкция, насколько я понимаю, реализована совсем не так, как ты расписал. Добавлено Цитата shm @ Так, а кто ж спорит-то? Кстати эта функция именно в таком виде используется в реализации практически всех примитивов синхронизации. |
Сообщ.
#38
,
|
|
|
Цитата Jin X @ тогда без вопросов именно. Цитата Jin X @ Это код из свежего Intel Instruction Reference Это псевдо-код поясняющий поведение инструкции. Он не имеет никакого отношения к ее аппаратной реализации. Добавлено Цитата Jin X @ Так, а кто ж спорит-то? А тогда ивзини, я неправильно понял твою реплику про cmpxchg. Тогда leo правильно написал. |
Сообщ.
#39
,
|
|
|
Нашёл интересную вещь в недрах AcquireSRWLockExclusive: NtWaitForKeyedEvent (ntdll.dll).
Возможно, это то, что мне нужно (если вернуться к первоначальной теме)... Буду изучать... http://www.locklessinc.com/articles/keyed_events/ |
Сообщ.
#40
,
|
|
|
Jin X, так обычные события чем не угодили я все понять не могу?
|
Сообщ.
#41
,
|
|
|
Уффф... Надо делать Init и Detele (в которых делается Init и Close... ну или просто Delete (Close) (либо создавать и удалять на каждом входе/выходе в/из крит.секции). Они более громоздкие/долгие. Надо делать отдельное событие под каждую критическую секцию (под каждый мьютекс). Короче, этот вариант мне кажется более интересным. Правда, это не совсем то, что я подумал изначально. Изначально мне казалось, что это альтернатива Sleep(1). Но всё равно лучше, чем Event. Вероятно. Я ещё не изучил этот вопрос
|
Сообщ.
#42
,
|
|
|
Цитата Jin X @ Надо делать Init и Detele Ой да целых пара наносекунд при запуске и завершении твоей программы, ога. Цитата Jin X @ либо создавать и удалять на каждом входе/выходе в/из крит.секции Не надо ничего создавать и удалять во время работы. Надо: Enter ... Leve инициализацию и удаления нужно вынести из основного цикла. Цитата Jin X @ Надо делать отдельное событие под каждую критическую секцию Цитата Jin X @ Но всё равно лучше, чем Event Что лучше? |
Сообщ.
#43
,
|
|
|
Цитата Jin X @ Нашёл интересную вещь в недрах AcquireSRWLockExclusive: NtWaitForKeyedEvent (ntdll.dll). Возможно, это то, что мне нужно (если вернуться к первоначальной теме)... Во-первых, NtWaitForKeyedEvent работает через тот же event, с той разницей, что 1) к одному эвенту можно прицепить несколько ключей (что несколько замедляет быстродействие, о котором ты так печешься), 2) если вместо первого параметра передать nil\null, то будет использован системный эвент (типа CritSecOutOfMemoryEvent) - возможно это "то, что тебе нужно", если тебе так хочется избежать "лишних" Create\Close, Init\Delete и т.п. Во-вторых, механизм использования NtWaitForKeyedEvent давно встроен в виндовые крит.секции (по кр.мере со времен XP) на случай облома создания собственного эвента крит.секции (по причине OutOfMemory или исчерпания лимита хэндлов). Поэтому, если тебе так нравится изобретать нестандартные велосипеды, то можно использовать небольшой хак с обычной крит.секцией - сразу после InitializeCriticalSectionAndSpinCount(cs,...), установить cs.LockSemaphore:=INVALID_HANDLE_VALUE - и всё, получишь два в одном: и готовый спин-луп с заданным числом повторений и ожидание NtWaitForKeyedEvent на системном эвенте (вместо создания собственного эвента для крит.секции и его ожидания на WaitForSingleObject). |
Сообщ.
#44
,
|
|
|
leo, интересно...
Почему же AcquireSRWLockShared, AcquireSRWLockExclusive не используют обычные Event'ы, а работают с KeyedEvent'ами? |
Сообщ.
#45
,
|
|
|
Цитата Jin X @ Почему же AcquireSRWLockShared, AcquireSRWLockExclusive не используют обычные Event'ы, а работают с KeyedEvent'ами? Потому, что это slim - упрощенный "до безобразия" вариант крит.секции. Структура SRWLOCK это набор битовых полей, упакованных в размер указателя (32 или 64 бита). В ней просто нет места не только для указателя на event, но и для OwningThreadID и RecursionCount (со всеми вытекающими особенностями использования по сравнению с крит.секцией). Индивидуального SpinCount-а тоже нет, используется некое дефолтное значение. Соотв-но и для ожидания освобождения лока используется не "обычный", а некий глобальный системный KeyedEvent. PS: Что касается быстродействия поиска ключа, то оно зависит от кол-ва, этих самых ключей, привязанных к одному KeyedEvent-у, ну и от используемой структуры хранения и алгоритма поиска ключей. В XP KeyedEvent использовался только в критической ситуации нехватки памяти, поэтому о быстродействии особо не беспокоились и использовали для хранения ключей простой список. А начиная с висты KeyedEvent-ы стали использовать для SRW-lock, условных переменных и т.п., и соотв-но стали хранить ключи в хэш-таблице для ускорения их поиска. Однако как бы не было реализовано хранение и поиск ключей, при малом\небольшом их кол-ве накладные расходы на их поиск\добавление\удаление составляют незначительный процент от общего времени ожидания эвента на NtWaitForKeyedEvent -> WaitForSingleObject |