как думаете можно ли этим пользоватся в винде учитывая что винда сможет в любой момент изменить приоритет потока с приоритетом в динамическом диапазоне ? алгоритм содержится в книге Э. Таненбаума Операционные системы /* Алгоритм Петерсона */ Код (Text): #include <stdio.h> #include <windows.h> #define N 2 static int turn; static int interested[N]; void enter_region(int thread) { int other; other = 1 - thread; interested[thread] = TRUE; turn = thread; while (turn == thread && interested[other] == TRUE); } void leave_region(int thread) { interested[thread] = FALSE; } static volatile long test_var; DWORD WINAPI Thread(PVOID pvContext) { int i = 0; while (i++ < 100000) { enter_region((int)pvContext); test_var++; leave_region((int)pvContext); } return 0; } int main(void) { HANDLE hArr[N]; HANDLE hCrProcess; int i; hCrProcess = GetCurrentProcess(); SetPriorityClass(hCrProcess, NORMAL_PRIORITY_CLASS); SetProcessPriorityBoost(hCrProcess, TRUE); for (i = 0; i < N; i++) hArr[i] = CreateThread(NULL, 0, Thread, (PVOID)i, 0, NULL); WaitForMultipleObjects(i, hArr, TRUE, INFINITE); for (i = 0; i < N; i++) CloseHandle(hArr[i]); printf("%d\n", test_var); return 0; }
Врезка из книги теперь поправочка касательно Венды во первых в Венде можно создать процесс в диапазоне приоритетов с реальным временем тогда потоки процесса не будут динамически изменять приоритет и второе даже в диапазоне с динамическими приоритетами если планировщик видит что какой то поток голодает то повышает ему приоритет чтобы он мог исполнится потом постепенно понижая приоритет собственно высказывайте свои мнения по поводу этого алгоритма можно ли им пользоватся при организации спинлоков например с последующим переходом в режим ядра примерно как InitializeCriticalSectionAndSpinCount
osox Какой-то отфанарный набор фактов сгреблён в кучу. В чём вопрос, не ясно. Алгоритм Петерсона никакого отношения не имеет ни к винде, ни к инверсии, ни к динамическим приоритетам, ни к планированию в принципе. Поэтому независимо от планирования и ОС алгоритм использовать можно, но смысла не имеет. Его недостаток в том, что он всегда рассчитан на заранее известное число потоков. Приведенные enter/leave несмотря на параметризацию N подходят исключительно для двух потоков. Изменение N на число неравное двум сделает алгоритм неработоспособным. Ввиду налагаемого ограничения на число потоков применять алгоритм имеет смысл только при отсутствии подходящих атомарных инструкций у процессора (xchg у x86, swp у ARM, tas у MC68000 и т.п.). Кроме того busy waiting вообще никогда не был желательным. Поэтому встроенные в Windows ф-ии синхронизации используют busy waiting в минимально возможных количествах. Вывод: применение бессмысленно.
l_inc ну как же не имеет отношения к планированию черным по белому написано что если в системе не заложено выделение квантов голодающим потокам то поток с более высоким приритетом при выходе из операции ввода вывода с повышенным приоритетом войдет в ожидание и фактически второй поток не выйдет из критической секции так как первый с высоким приритетом будет постоянно время забирать или другой сценарий у потоков одинаковые приритеты тогда этой проблемы не возникнет отсюда я и упомянул про приоритеты и планировщик разве нет связи ?
l_inc разные оси разные планировщики одни ориентированны на пакетную обработку другие системы реального времени третьи интерактивные во всех этих случаях планировщик затачивается под конкретные нужды связь очевидна вставка про планировщик винды была сделана из за этого странно что Вы связи не видите этот алгоритм поведет себя по разному на системах с разными планировщиками связь все таки прямая
l_inc и кстати Вы очень сильно ошибаетесь по поводу заточености алгоритма под два потока алгоритм расширяется это адлгоритм Декера не расширяется он действительно под два процесса или потока заточен а этот расширяется
osox Проблема инверсии (которая, кстати, тоже совсем по другому выглядит) имеет отношение к взаимоисключающему доступу в принципе, а не к алгоритму Петерсона. Критические секции, как метод реализации взаимоисключающего доступа, есть необходимое средство в наборе возможностей ОС. Каким именно образом оно реализовано — вопрос десятый. Поэтому ещё раз: алгоритм Петерсона здесь никаким боком. Что значит "по разному"? Если реализация соответствует условиям (например, алгоритм для 2-х потоков и не более 2-х потоков работают в системе), то никаких "по-разному" не будет. Будет, как и планировалось, взаимоисключающий доступ. А если есть голодание, то с тем же успехом оно было бы и у спинлока на атомарной инструкции. Начнём с того, что я не говорил, что алгоритм нельзя переделать под другое число потоков. Я сказал, что приведенные enter/leave будут работать только для двух потоков. А то, что Петерсон расширяется на любое заранее фиксированное число потоков, — это, разумеется, верно.
l_inc еще вопрос есть я часто в исходных кодах вижу что тип указателя на *void часто приводится явно кастом (type*)(void*) к нужному типу например после malloc в то же время достаточно много литература по ansi c конверитруют из void* в нужный тип без явного каста ptr = malloc(); к примеру последняя книга которую я читаю там из void* кастится в другой тип без явного каста тоесть конвертируется из (void*)->(type*) и из (type*)->(void*) без явного каста книга по стандарту ansi c так как же все таки правильно поступать в языке С просветите )
l_inc есть подозрение что авторы часто не удосуживаются указать язык того на чем они писали если для плюсов код то тогда там вроде void* и другие не совместимы при такой конвертации (void*)->(type*) а обратно (type*)->(void*) можно поэтому и встречаются разные коды на многих форумах например с пометкой C++ сливают как С так и С++ да много где наблюдается несоответствие можно в чистом С делать без явного каста как считаете ?
osox В си можно, насколько мне известно. В плюсах компилятор должен выдать как минимум предупреждение. К указателю на void можно везде приводить неявно. Но вообще это уже не ко мне. Я некомпетентен по вопросам стандартов. Моё правило такое: если компилятор ругается, то надо трижды подумать, имел ли я в виду именно это, а потом откастить.
l_inc ясно спасибо надоела эта путаница с кастом смотриш одни исходники кастится другие смортиш нет сейчас посмотрел K&R ANSI C там снова кастится в разделе структуры пример с деревом поиска функа из книги struct tnode *talloc(void) { return (struct tnode*)malloc(sizeof(struct tnode)); } затем открыл книгу искуство программирования на си тоже автор всю дорогу рассказывает что надо придерживатся стандарта в тоже время по всей книге прмеры типа typedef int *T; T a = malloc(); или char *p; p = malloc(); автор с 20 стажем или около того пишет тоже про ANSI C почему код разный не пониманию вообщем вопрос до конца не ясен каждый раз когда приходится присваивать из void*->type* эта неопределенность определено начинает надоедать
l_inc вот что нашел в K&R ANSI C электронная версия вообщем как понимаю до ANSI C надо было явно приводить (void*)->(type*) в ANSI C как я понял это не обязательно и даже может скрыть случайную ошибку а вот K&R ANSI C бумажная у меня дома КТО ПИШЕТ ЭТИ КНИГИ ? КАЗНИТЬ !!!
l_inc или вот еще пример из Си я часто использую каллбеки например удобно передавать функциям обработки связного списка например для ForEachList(ProxyList, Attack); у ForEachList такой прототип typedef VOID(*ForEach)(PVOID Item, DWORD Tag, SIZE_T Size); VOID ForEachList(DLLIST List, ForEach pfn); ForEach принимает PVOID так как в списке можно хранить объекты разной структуры различаю по tag теперь возмем частый случай у меня в списке объеты одного типа например строки адреса проксей и функцию свою я определяю так VOID Attack(CONST PCHAR Proxy, DWORD Tag, SIZE_T Size) { /* body */ } и передаю ForEachList(ProxyList, Attack); можно ли вместо VOID Attack(PVOID Proxy, DWORD Tag, SIZE_T Size) { CONST PCHAR pszProxy = Proxy; /* body */ } писать сразу VOID Attack(CONST PCHAR Proxy, DWORD Tag, SIZE_T Size) { /* body */ } избавляемся от каста тем более что void* и anothertype* взаимозаменяемы какой смысл внутри делать указатель на другой тип если можно параметром записать так удобнее по крайней мере мне вот этот вопрос меня тоже интересует можно так делать или нет ?
osox Я так понимаю, что при вызове ForEachList(ProxyList, Attack); мы кастим уже не PCHAR в PVOID, а всё таки VOID(*)(PVOID Item, DWORD Tag, SIZE_T Size); в VOID(*)(CONST PCHAR Item, DWORD Tag, SIZE_T Size);. Такой кастинг указателей уже не прокатит бесследно. Но, как я уже сказал, я — не спец. Так что зря Вы мой ник прям в каждый пост заголовком.
l_inc сегодня случайно прочитал в одной книге Herbert Schildt - C: The Complete Reference, 4th Edition В языке С допускается присваивание указателя типа void * указателю любого другого типа (и наоборот) без явного преобразования типа указателя. Тип указателя void * используется, если тип объекта неизвестен. Например, использование типа void * в качестве параметра функции позволяет передавать в функцию указатель на объект любого типа, при этом сообщение об ошибке не генерируется. В языке C++ требуется явно указывать преобразование типа указателей, в том числе указателей типа void *. Поэтому многие программисты используют в языке С явное преобразование для совместимости с C++.
а сейчас когда в с99 много разных новых свойств которые с++ никогда не скомпилит заведомо можно и не беспокоится о совместимости не писать же на Си старой редакции только для совместимости с Си++ ?
кстати а какой смысл в том что в C++ указателю void * можно присвоить любой тип указателя а обратно нельзя ? в С и туда и обратно можно