Здравствуйте, barney, Вы писали:
N>>1. Что за компилятор?
B>Apple LLVM version 9.1.0
Я удивляюсь стилю ассемблера, GCC и Clang используют стиль GCC. Но тут явно что-то другое, ближе к традициям MS. OK, ясно.
N>>CF=0 как раз если было 0, то есть спинлок захвачен. Нафига в этом случае возвращаться на loop? Ты делаешь всё наоборот — если захватил, идёшь на круг N>>Тут лучше ложится jc outer (и вообще, outer и loop совместить)
B>ммм... моя логика такая: B>если мьютекс еще не захвачен ( == 0) то вызов 'bts [rbx], eax' в керри положит старое значени 0, и атомарно в память 1 B>(но, до этого момента, другой поток УЖЕ мог прочитать 0 в таком же outer цикле, и перейти на inner этап) B>теперь проц дает гарантию что lock bts атомарно положит там 1 хотя бы в одном из потоков! B>т.е у кого то carry будет == 0 а у кого == 1 B>вот у того, кого 0 — тот захватил первым
Именно. И если CF=0, то надо не идти на цикл, а считать действие lock завершённым. Поэтому jc должно идти на цикл, а противоположность (прямой ход исполнения или jnc) — выходить из цикла.
Но дальше второе — чтобы не задрочить кэш, шины и всю систему постоянными попытками записи, если bts или аналог сорвались, снова идут на циклическое чтение, в ожидании, пока не появится 0.
Здравствуйте, netch80, Вы писали:
N>Только когда нет работы. Когда она есть — им незачем спать.
Да, но есть риск получить систему полностью загруженной, когда она перестанет отзываться на действия пользователя.
Вполне возможно, что в цикле рабочего потока ::Sleep(100) — не вызовет катастрофических задержек.
Однако, вызвав диспетчер потоков позволит пользователю как-то общаться с машиной, когда запущена твоя вычислительная задача.
В противном случае — компьютер становиться страшно тормознутым и практически не управляемым
N>Именно. И если CF=0, то надо не идти на цикл, а считать действие lock завершённым. Поэтому jc должно идти на цикл, а противоположность (прямой ход исполнения или jnc) — выходить из цикла. N>Но дальше второе — чтобы не задрочить кэш, шины и всю систему постоянными попытками записи, если bts или аналог сорвались, снова идут на циклическое чтение, в ожидании, пока не появится 0.
Вот сделал без ассемблера, с std::atomic_flag по стопам приведенной тут в ссылках статьи
N>Именно. И если CF=0, то надо не идти на цикл, а считать действие lock завершённым. Поэтому jc должно идти на цикл, а противоположность (прямой ход исполнения или jnc) — выходить из цикла.
Точно!! JC ведь прыгает когда CF=1, те другой поток уже ухватил
Я пофиксил и заработало как надо
N>Но дальше второе — чтобы не задрочить кэш, шины и всю систему постоянными попытками записи, если bts или аналог сорвались, снова идут на циклическое чтение, в ожидании, пока не появится 0.
Вот, поэтому и нужен внешний цикл, который читает и ждет освобождения
и это можно делать неатомарно
Здравствуйте, barney, Вы писали:
N>> while(!lck.test_and_set(std::memory_order_acquire))
B>все таки без !
Да, это я стормозил.
B>занятно что CLANG 6.0 вместо lock bts B>делает вот что:
B>
B>.LBB0_1: # =>This Inner Loop Header: Depth=1
B> mov al, 1
B> xchg byte ptr [rip + lock], al
Ну так это test_and_set превращается в такой атомарный обмен с единицей.
Можно попросить Clang показать, как это выглядит на уровне его LL-представления. Мне 4.0 выдал такое:
[code]
%5 = atomicrmw xchg i8* getelementptr inbounds ({ { i8 } }, { { i8 } }* @lock, i64 0, i32 0, i32 0), i8 1 acquire
B>он xchg считает атомарной инструкцией... странно это все...
Ничего странного, что он так считает, доки читать надо. Из интеловского мануала про XCHG:
If a memory operand is referenced, the processor’s locking protocol is automatically implemented for the duration of the exchange operation, regardless of the presence or absence of the LOCK prefix or of the value of the IOPL. (See the LOCK prefix description in this chapter for more information on the locking protocol.)
This instruction is useful for implementing semaphores or similar data structures for process synchronization. (See “Bus Locking” in Chapter 8 of the Intel® 64 and IA-32 Architectures Software Developer’s Manual, Volume 3A, for more information on bus locking.)
Это Intel знатные извращенцы, они любое изменение в ISA продумывают даже не на один шаг, а на полшага. Сначала надо было чем-то сделать атомарные операции — подумали, что делается только на обмене, цену проигнорировали, добавили такое поведение в XCHG. Потом узнали (хотя все нормальные люди знали давно), что к обмену далеко не всё сводится, стали дорабатывать другие операции (начиная со всяких ADD и BTS), убрали умолчание LOCK. Самое смешное, что при этом для инструкций, которые по определению используются только для атомарных операций — CMPXCHG, XADD — не сделали такого умолчания, и им надо писать явно LOCK (а XCHG — не надо)! Кого бог хочет наказать, лишает разума...
Здравствуйте, AlexGin, Вы писали:
AG>Здравствуйте, netch80, Вы писали:
N>>Только когда нет работы. Когда она есть — им незачем спать. AG> AG>Да, но есть риск получить систему полностью загруженной, когда она перестанет отзываться на действия пользователя.
Не знаю, при каком шедулере такое возможно.
В нормальном, пусть даже при N ядрах у нас N рабочих тредов в пуле, задача реакции на пользователя всё равно получит свои кванты, хоть и заметно меньше.
Если же сделать N-1 рабочий тред, то должно хватить места на заметную активность всех других видов.
(Считаю, что других задач на машине не крутится. А вообще для таких вещей надо делать конфигурируемый параметр.)
AG>Вполне возможно, что в цикле рабочего потока ::Sleep(100) — не вызовет катастрофических задержек. AG>Однако, вызвав диспетчер потоков позволит пользователю как-то общаться с машиной, когда запущена твоя вычислительная задача. AG>В противном случае — компьютер становиться страшно тормознутым и практически не управляемым
Уточните, под какой ОС такое происходит. Честно, у меня как-то не наблюдалось. Но у меня уже лет 20 что сервера, что десктопы/лаптопы почти сплошь Linux и FreeBSD.
Но в общем случае я поправку принимаю, с нормальными шедулерами она не должна сильно помешать, а с кривыми — поверю, что помогает.
Здравствуйте, AlexGin, Вы писали:
AG>Да, но есть риск получить систему полностью загруженной, когда она перестанет отзываться на действия пользователя. AG>Вполне возможно, что в цикле рабочего потока ::Sleep(100) — не вызовет катастрофических задержек. AG>Однако, вызвав диспетчер потоков позволит пользователю как-то общаться с машиной, когда запущена твоя вычислительная задача. AG>В противном случае — компьютер становиться страшно тормознутым и практически не управляемым
Лучше уменьшить приоритет вычислительной задаче через системный менеджер процессов, например, или из кода.
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)
Здравствуйте, andrey.desman, Вы писали:
AD>Серьезно? А как же очередь ожидающих и приоритизация? То есть, какой-то один поток может бесконечно долго висеть и не захватить мьютекс?
Я разве утверждал что ее там нет. Там очередь также реализована в user mope в виде списка с добавлением новых потоков в ее конец. Может быть вы все-таки глянете исходники? Так как std::mutex может "шариться" только внутри одного процесса, то я не вижу проблем всю логику реализовать вне ядра. Вы не согласны?
Здравствуйте, Videoman, Вы писали:
V>Я разве утверждал что ее там нет. Там очередь также реализована в user mope в виде списка с добавлением новых потоков в ее конец. Может быть вы все-таки глянете исходники? Так как std::mutex может "шариться" только внутри одного процесса, то я не вижу проблем всю логику реализовать вне ядра. Вы не согласны?
Для освобождения процессора необходимо перейти в режим ядра. Инструкция pause, про которую ты говорил, нужна для spinlock, но для mutex ее недостаточно.
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)
Здравствуйте, AlexGin, Вы писали:
AG>Всё зависит от задач. AG>Я обычно мьютексы не использую, так как это "тяжёлые" системные объекты. AG>Насчет критических секций — не вижу ничего предосудительного в их применении. AG>Часто я аккуратно пользуюсь ими. Здесь важно — НЕ вводить в облась, защищенную секцией много кода: AG>
AG>::EnterCriticalSection(m_pCS);
AG>// Самые элементарные действия с общими ресурсами
AG>::LeaveCriticalSection(m_pCS);
AG>
Ну и зря. Под Windows, внутри Microsoft CRT, std::mutex не имеет ничего общего с WinAPI Mutex. Там, грубо, простой test_and_set, без захода в ядро.
AG>Заполнять ядра CPU работой — это палка о двух концах: AG>При работе таких приложений, компьютер очень медленно реагирует на действия пользователя. AG>Правильнее — где-то в циклах рабочих потоков вводить ::Sleep(100...500); AG> — чтобы отдать управление системе (исключить жуткие тормоза для end-user-а).
Вот ни разу не встречал такого (только если не долбиться с тяжелым запросом в ядро в цикле). Всегда свои программы профилировали чтобы загрузка СPU была 100%. Если меньше, значит где-то холостые ожидания — значит код не оптимален, с точки зрения многопоточности.
Здравствуйте, lpd, Вы писали:
lpd>Для освобождения процессора необходимо перейти в режим ядра. Инструкция pause, про которую ты говорил, нужна для spinlock, но для mutex ее недостаточно.
Возможно я не внимательно смотрел. В любом случае, если spin-lock не успел — деваться некуда, придется уходить в ядро, но это гораздо эффективней чем просто пользоваться Mutex-ом.
Здравствуйте, netch80, Вы писали:
N>Не знаю, при каком шедулере такое возможно. N>В нормальном, пусть даже при N ядрах у нас N рабочих тредов в пуле, задача реакции на пользователя всё равно получит свои кванты, хоть и заметно меньше. N>Если же сделать N-1 рабочий тред, то должно хватить места на заметную активность всех других видов. N>(Считаю, что других задач на машине не крутится. А вообще для таких вещей надо делать конфигурируемый параметр.)
AG>>Вполне возможно, что в цикле рабочего потока ::Sleep(100) — не вызовет катастрофических задержек. AG>>Однако, вызвав диспетчер потоков позволит пользователю как-то общаться с машиной, когда запущена твоя вычислительная задача. AG>>В противном случае — компьютер становиться страшно тормознутым и практически не управляемым
N>Уточните, под какой ОС такое происходит.
Последний раз видел такую ситуацию — примерно пять лет назад под Windows XP.
С тех пор я стараюсь (в моих разработках) давать возможность переключиться на диспетчер потоков — это Sleep или WaitingFor... системные вызовы.
N>Честно, у меня как-то не наблюдалось. Но у меня уже лет 20 что сервера, что десктопы/лаптопы почти сплошь Linux и FreeBSD.
А разве в мире Linux что-то принцыпиально иначе? Разве не будет такой же ситуации?
Если на каждом из ядер крутится свой поток, и не даёт возможности "вклиниться" диспетчеру потоков, то там должно быть аналогично.
Хотя, допускаю, что при определенных настройках для Linux этот эффект тормозов можно сделать не так заметным.
N>Но в общем случае я поправку принимаю, с нормальными шедулерами она не должна сильно помешать, а с кривыми — поверю, что помогает.
Предположим, что у нас обычный офисный комп с Win10/Win7.
У меня есть приложение, которое (выполняя определенную работу) — дико тормозит все мои пользовательские действия.
Что я могу сделать, как пользователь? Что могу сделать, если приложение сторонее и я НЕ имею его исходных кодов?
Здравствуйте, Videoman, Вы писали:
AD>>Серьезно? А как же очередь ожидающих и приоритизация? То есть, какой-то один поток может бесконечно долго висеть и не захватить мьютекс?
V>Я разве утверждал что ее там нет. Там очередь также реализована в user mope в виде списка с добавлением новых потоков в ее конец. Может быть вы все-таки глянете исходники? Так как std::mutex может "шариться" только внутри одного процесса, то я не вижу проблем всю логику реализовать вне ядра. Вы не согласны?
Не согласен, проблемы будут. Вне ядра получится слишком топорно.
Под Windows, например (VS2013), std::mutex реализован без использования вызовов WinAPI, почти так, как вы и описали, но spin блокировка крутится заданное число циклов (сейчас 4000) и уже по истечении этого времени квант потока отдается (он засыпает на pause). Фактически в С++ рантайм вынесены "кишки" стандартной critical section.
Без использования вызовов WinAPI. Нет.
Квант потока отдается (он засыпает на pause). Нет.
почти так, как вы и описали, но в описании был топорный спин-лок.
Нет, конечно, не утверждал, но учитывая вышенаписанное, ты скорее сам исходники смотрел не шибко внимательно.
Здравствуйте, Videoman, Вы писали:
V>Здравствуйте, AlexGin, Вы писали:
AG>>Всё зависит от задач. AG>>Я обычно мьютексы не использую, так как это "тяжёлые" системные объекты. AG>>Насчет критических секций — не вижу ничего предосудительного в их применении. AG>>Часто я аккуратно пользуюсь ими. Здесь важно — НЕ вводить в облась, защищенную секцией много кода: AG>>
AG>>::EnterCriticalSection(m_pCS);
AG>>// Самые элементарные действия с общими ресурсами
AG>>::LeaveCriticalSection(m_pCS);
AG>>
V>Ну и зря. Под Windows, внутри Microsoft CRT, std::mutex не имеет ничего общего с WinAPI Mutex. Там, грубо, простой test_and_set, без захода в ядро.
Тогда отлично.
Можно им пользоваться достаточно спокойно!
AG>>Заполнять ядра CPU работой — это палка о двух концах: AG>>При работе таких приложений, компьютер очень медленно реагирует на действия пользователя. AG>>Правильнее — где-то в циклах рабочих потоков вводить ::Sleep(100...500); AG>> — чтобы отдать управление системе (исключить жуткие тормоза для end-user-а).
V>Вот ни разу не встречал такого (только если не долбиться с тяжелым запросом в ядро в цикле).
Хорошо, — вот допустим, что у меня такой вариант "ожидания":
Теперь предположим, что в результате некоторой ситуации все ядра моего CPU ждут на while в методе lock.
Да, случай практически не вероятный, но всё-таки — полностью НЕ исключаемый.
Загрузка всех секций CPU будет под 100% (здесь я даже не спрашиваю насчет полезноти/бесполезности данной вычислительной работы).
Спрошу только одно — сможет ли пользователь хоть как-то взаимодействовать с ОС? Хоть бы для того, чтобы снять эту зависшую задачу?
V>Всегда свои программы профилировали чтобы загрузка СPU была 100%. Если меньше, значит где-то холостые ожидания — значит код не оптимален, с точки зрения многопоточности.
+100500
Да, с точки зрения многопоточности НЕ оптимален.
Но если я вынужден буду только "топить Reset", чтобы хоть как-то вмешаться в работу системы, то такой код также не является корректным
Здравствуйте, andrey.desman, Вы писали:
AD>Здравствуйте, AlexGin, Вы писали:
AG>>Всё зависит от задач. AG>>Я обычно мьютексы не использую, так как это "тяжёлые" системные объекты.
AD>С чего они тяжелые? Или речь о Windows?
+100500
Да, именно про Windows!
Здравствуйте, AlexGin, Вы писали:
AG>while(lck.test_and_set(std::memory_order_acquire)) [b]// здесь, возможно, придется подождать!
Спинлок в такой имплементации — это вообще, как по мне, плохая идея.
Во-первых, постоянно теребить глобальную переменную с xchg/cmpxchg и блокировкой шины — не есть гуд
(bus traffic и все такое). Как минимум, тут надо применять стратегию не "test and set", а
"test test and set" + какую-то разгрузку в виде yield/pause/Sleep/etc.
Во-вторых, на однопроцессорной машине крутиться в спин-цикле бессмысленно, так как лок никто в
это время не освободит.
Ну и в-третьих, если между захватом и освобождением лока поток будет вытеснен, получится
очень некрасивая ситуация по отношению к другим потокам, которые ждут его освобождения.
Особенно если лок не гарантирует порядок захвата. Так легко и систему завесить, особенно если
такой спинлок применять где-нибудь в критических местах — драйверы, всякие системные
обработчики и т.д. Встречал на практике.