Здравствуйте, Videoman, Вы писали:
V>Под Windows, например (VS2013), std::mutex реализован без использования вызовов WinAPI, почти так, как вы и описали, но spin блокировка крутится заданное число циклов (сейчас 4000) и уже по истечении этого времени квант потока отдается (он засыпает на pause). Фактически в С++ рантайм вынесены "кишки" стандартной critical section.
Серьезно? А как же очередь ожидающих и приоритизация? То есть, какой-то один поток может бесконечно долго висеть и не захватить мьютекс?
коллективный разум это когда решается какая то проблема доселе не известная или не имеющая решения
а как устроены ОС описано в гугле на каждом шагу, плюс куча книг и исходников
это называется по другому, — научите меня бесплатно
это все равно что если бы я создал тему "как устроен С++ ?"
и объясняйте мне на пальцах все о нём
ну т.е. ваша тема флеймовая, — сам читать не хочу, объясняйте мне своими словами
Здравствуйте, AlexGin, Вы писали:
AG>Здравствуйте, netch80, Вы писали:
N>>Только когда нет работы. Когда она есть — им незачем спать. AG> AG>Да, но есть риск получить систему полностью загруженной, когда она перестанет отзываться на действия пользователя.
Не знаю, при каком шедулере такое возможно.
В нормальном, пусть даже при N ядрах у нас N рабочих тредов в пуле, задача реакции на пользователя всё равно получит свои кванты, хоть и заметно меньше.
Если же сделать N-1 рабочий тред, то должно хватить места на заметную активность всех других видов.
(Считаю, что других задач на машине не крутится. А вообще для таких вещей надо делать конфигурируемый параметр.)
AG>Вполне возможно, что в цикле рабочего потока ::Sleep(100) — не вызовет катастрофических задержек. AG>Однако, вызвав диспетчер потоков позволит пользователю как-то общаться с машиной, когда запущена твоя вычислительная задача. AG>В противном случае — компьютер становиться страшно тормознутым и практически не управляемым
Уточните, под какой ОС такое происходит. Честно, у меня как-то не наблюдалось. Но у меня уже лет 20 что сервера, что десктопы/лаптопы почти сплошь Linux и FreeBSD.
Но в общем случае я поправку принимаю, с нормальными шедулерами она не должна сильно помешать, а с кривыми — поверю, что помогает.
Здравствуйте, barney, Вы писали:
B>Интересно, как реализованы базовые примитивы мультипоточности в C++? Такие как mutex
B>но это приводит не просто к простою потока, а постоянной нагрузке
B>при использовании std::mutex для критической секции такого не наблюдается.
B>как же работает мютекс?
В linux mutex работает через системный вызов futex, который освобождает процессор и добавляет поток в очередь, если мьютекс занят.
Документация: manual
Можно почитать здесь: статья
Также есть комментарии в коде ядра futex.c
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)
R>какой смысл их исследовать ? R>в юниксе код открыт, хотите открывайте и читайте R>в винде WRK тоже открыт + windows internal pdf есть
Не исследуйте, кто ж вас заставляет)
Мне интереснее человеческое общение на данные темы.
Например, книге нельзя задать вопрос по мере его возникновения.
Поэтому лично мне интереснее исследовать тему коллективным разумом.
Здравствуйте, barney, Вы писали:
B>Т.е должен быть механизм, который отличает поток, ждущий futex от работающего потока, квант времени которого истек. B>Иначе нет строгих гарантий что futex поток не проснется "внезапно" если планировщику вдруг так вздумается. B>Т.е есть какой то системный condition variable в ядре, по которому поток "вешается" спать, до наступления сигнала ядра, B>которое, например возникает когда другой поток освобождает тот же мьютекс?
Да, у futex в ядре есть очередь ожидающих его потоков.
Код mutex.lock() в user-level атомарно проверяет флаг занятости мьютекса, и, если он свободен, то просто захватывает его, и без вызова кода ядра возвращает управление захватывающему мьютекс коду. Если мьютекс уже кем-то занят, то mutex.lock() вызывает сисколл sys_futex(FUTEX_WAIT). Есть тонкость, что sys_futex в ядре еще раз проверяет занятость мьютекса на случай, если он освободился между проверкой mutex.lock() и sys_futex()(Это подробно описано в комментарии в коде ядра futex.c). Если фьютекс занят и поток нужно перевести в режим ожидания, то он добавляется в очередь, связанную в ядре с фьютексом.
Когда занимающий фьютекс поток освободит его вызовом mutex.unlock()(реализованный через сисколл sys_futex(FUTEX_WAKE)), код sys_futex(FUTEX_WAKE) в ядре выберет ожидающий поток из очереди фьютекса и пометит его системному планировщику(у которого есть свой глобальный список потоков) для исполнения. Этот поток когда до него дойдет очередь получит квант времени, на него переключится контекст, он вернется из sys_futex(FUTEX_WAIT), и окажется в коде mutex.lock(). Код mutex.lock() на всякий случай проверит, не был ли снова занят mutex, и если успеет атомарно захватит его.
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)
B>возникла идея безумная. вот смотрите, ведь все равно- потоки используют общую память. т.е им не нужен "защищенный режим" изоляции друг от друга B>можно ли на уровне ОС сделать "user mode" потоки? B>например юзермодовское прерывание таймера, которое не меняет контекст (user to kernel)? B>т.о. потоки в рамках одного процесса могут быстро останавливаться — юзермод таймер прерыванием — быстро переключать стек регистров — и продолжать управление. B>т.е если общий квант времени, отведенного на процесс не истек — то получим сверхшустрые потоки в рамках этого кванта
Здравствуйте, barney, Вы писали:
lpd>>Переход в режим ядра и тем более переключение контекста на другой поток — достаточно долгие операции.
B>возникла идея безумная. вот смотрите, ведь все равно- потоки используют общую память. т.е им не нужен "защищенный режим" изоляции друг от друга B>можно ли на уровне ОС сделать "user mode" потоки?
Раньше лет 15 назад в Linux потоки так и были реализованы без sys_futex(). Детали не знаю, но для переключения исполнения между потоками можно использовать user-level libc-функции setjmp()/longjmp().
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)
Здравствуйте, netch80, Вы писали:
N>Только когда нет работы. Когда она есть — им незачем спать.
Да, но есть риск получить систему полностью загруженной, когда она перестанет отзываться на действия пользователя.
Вполне возможно, что в цикле рабочего потока ::Sleep(100) — не вызовет катастрофических задержек.
Однако, вызвав диспетчер потоков позволит пользователю как-то общаться с машиной, когда запущена твоя вычислительная задача.
В противном случае — компьютер становиться страшно тормознутым и практически не управляемым
Здравствуйте, AlexGin, Вы писали:
AG>Да, но есть риск получить систему полностью загруженной, когда она перестанет отзываться на действия пользователя. AG>Вполне возможно, что в цикле рабочего потока ::Sleep(100) — не вызовет катастрофических задержек. AG>Однако, вызвав диспетчер потоков позволит пользователю как-то общаться с машиной, когда запущена твоя вычислительная задача. AG>В противном случае — компьютер становиться страшно тормознутым и практически не управляемым
Лучше уменьшить приоритет вычислительной задаче через системный менеджер процессов, например, или из кода.
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)
Интересно, как реализованы базовые примитивы мультипоточности в C++? Такие как mutex
насколько я понимаю, все базируется на атомарной test-and-set инструкции (которую я бы назвал fetch-and-set)
и spin_lock блокировке, которая выглядит примерно так:
spin_outer:
cmp flag, 1
je spin_outer // waiting until 0
spin_inner:
test-and-set ax, flag //executed atomicly
// {
// mov ax, flag // fetch current value to ax
// mov flag, 1 // set flag to 1
// }
cmp ax, 1
jne spin_inner // if old value was 0
// now in critical section
call do_work
mov flag, 0 // clean flag
leave:
но это приводит не просто к простою потока, а постоянной нагрузке
при использовании std::mutex для критической секции такого не наблюдается.
Здравствуйте, barney, Вы писали:
B>Интересно, как реализованы базовые примитивы мультипоточности в C++? Такие как mutex
Конкретная реализация целиком зависит от рантайма и на разных платформах реализована по разному.
B>это приводит не просто к простою потока, а постоянной нагрузке B>при использовании std::mutex для критической секции такого не наблюдается.
Под Windows, например (VS2013), std::mutex реализован без использования вызовов WinAPI, почти так, как вы и описали, но spin блокировка крутится заданное число циклов (сейчас 4000) и уже по истечении этого времени квант потока отдается (он засыпает на pause). Фактически в С++ рантайм вынесены "кишки" стандартной critical section.
V>Конкретная реализация целиком зависит от рантайма и на разных платформах реализована по разному.
Вот это и хочется изучить. Т.к не понятно, как именно мьютекс "усыпляет" поток, и кто потом его "просыпает"
как вообще работает эта машинерия.
Собственно для этого и создал данный топик
для тех кому интересно было бы исследовать и обсудить реализации для различных ОС / архитектур
V>Под Windows, например (VS2013), std::mutex реализован без использования вызовов WinAPI, почти так, как вы и описали, но spin блокировка крутится заданное число циклов (сейчас 4000) и уже по истечении этого времени квант потока отдается (он засыпает на pause). Фактически в С++ рантайм вынесены "кишки" стандартной critical section.
Понятно, любопытно. Т.е засыпание потока это уже функция ядра, и ядро же умеет "пробуждать" его, если где то в другом потоке мьютекс освободился?
т.е. ядро каким то образом связывает эти события
Здравствуйте, barney, Вы писали:
V>>Конкретная реализация целиком зависит от рантайма и на разных платформах реализована по разному.
B>Вот это и хочется изучить. Т.к не понятно, как именно мьютекс "усыпляет" поток, и кто потом его "просыпает" B>как вообще работает эта машинерия.
Системный вызов futex работает в ядре, и усыпляет поток передачей управления функции schedule() планировщика процессов. Вообще планировщик вызывается или напрямую если процесс сам хочет освободить процессор, или из прерывания таймера. В зависимости от приоритетов процессов планировщик выбирает тот, которому положен следующий квант времени, и переключает на него контекст переключением таблицы страниц и загрузкой сохраненных значений регистров этого процесса.
Когда мьютекс освобождается, код futex помечает один из ждущих в очереди фьютекса потоков для исполнения, и планировщик при случае передаст ему управление.
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)
lpd>Системный вызов futex работает в ядре, и усыпляет поток передачей управления функции schedule() планировщика процессов. Вообще планировщик вызывается или напрямую если процесс сам хочет освободить процессор, или из прерывания таймера. В зависимости от приоритетов процессов планировщик выбирает тот, которому положен следующий квант времени, и переключает на него контекст переключением таблицы страниц и загрузкой сохраненных значений регистров этого процесса.
Ага, schedule() вызывается и по прерыванию таймером работающего потока, и в futex, так?
Т.е должен быть механизм, который отличает поток, ждущий futex от работающего потока, квант времени которого истек.
Иначе нет строгих гарантий что futex поток не проснется "внезапно" если планировщику вдруг так вздумается.
lpd>Когда мьютекс освобождается, код futex помечает один из ждущих в очереди фьютекса потоков для исполнения, и планировщик при случае передаст ему управление.
Т.е есть какой то системный condition variable в ядре, по которому поток "вешается" спать, до наступления сигнала ядра,
которое, например возникает когда другой поток освобождает тот же мьютекс?
Здравствуйте, smeeld, Вы писали:
S>В разных реализациях STL и для разных ОС по-разному. Например, в GCC для unix-like-ов std::mutex-это приплюснутый врайпер над POSIX API.
А POSIX функция pthread_mutex_lock уже, я так понимаю, использует некие функции ядра?
в LINUX это futex
асли это DARWIN/MACH то есть некие ядерные mach_ функции для блокировки/просыпания потоков, которые завязаны на мьютекс
Здравствуйте, barney, Вы писали:
B>Мне интереснее человеческое общение на данные темы. B>Например, книге нельзя задать вопрос по мере его возникновения. B>Поэтому лично мне интереснее исследовать тему коллективным разумом.
Дело в том, что в зависимости, даже, от версии студии и стандарта С++ реализация может сильно наворочена для поддержки всех примитивов. Но в итоге, все сводится к старому доброму test_and_set. Под Windows вы также можете посмотреть реализацию в исходниках CRT. Microsoft вынесла всю реализацию в user mode. В случае необходимости усыпить поток, что бы он не тратил ресурсы процессора, используется ассемблерная вставка, вызывающая инструкцию pause, которая специально предназначена для ожидания внутри spin lock.
R>коллективный разум это когда решается какая то проблема доселе не известная или не имеющая решения R>а как устроены ОС описано в гугле на каждом шагу, плюс куча книг и исходников R>это называется по другому, — научите меня бесплатно
феерическая чепуха)) про учебные группы слышал? добровольные, заметь
прийди еще в школу, поругай там разборы сортировки пузырьком)
а просветленные гуру — пусть идут мимо,
никто ж не претендует на их "небесплатное" внимание, лол)
lpd>Да, у futex в ядре есть очередь ожидающих его потоков. lpd>Код mutex.lock() в user-level атомарно проверяет флаг занятости мьютекса, и, если он свободен, то просто захватывает его, и без вызова кода ядра возвращает управление захватывающему мьютекс коду. Если мьютекс уже кем-то занят, то mutex.lock() вызывает сисколл sys_futex(FUTEX_WAIT). Есть тонкость, что sys_futex в ядре еще раз проверяет занятость мьютекса на случай, если он освободился между проверкой mutex.lock() и sys_futex()(Это подробно описано в комментарии в коде ядра futex.c). Если фьютекс занят и поток нужно перевести в режим ожидания, то он добавляется в очередь, связанную в ядре с фьютексом. lpd>Когда занимающий фьютекс поток освободит его вызовом mutex.unlock()(реализованный через сисколл sys_futex(FUTEX_WAKE)), код sys_futex(FUTEX_WAKE) в ядре выберет ожидающий поток из очереди фьютекса и пометит его системному планировщику(у которого есть свой глобальный список потоков) для исполнения. Этот поток когда до него дойдет очередь получит квант времени, на него переключится контекст, он вернется из sys_futex(FUTEX_WAIT), и окажется в коде mutex.lock(). Код mutex.lock() на всякий случай проверит, не был ли снова занят mutex, и если успеет атомарно захватит его.
То есть, в общем случае мьютексная блокировка требует сисколла (если не брать быстрый спинлок у futex — и то, это только у линукс)
и, значит, как минимум, смену контекста на ядерный + перепланирование потока. что может привести к серьезному простою на исполнение в CPU
Притом, даже, если конкурирующие потоки очень быстро работают (например размалывая какие то данные параллельно) — их будет поочередно кидать в ядро...
Хмм... Стало быть, мьютексы — не очень эффективный механизм. Тут нужен какой то иной подход.
Та же work queue или thread pool
Хотя, насколько я понимаю condition variable в основе work queue — тоже системный вызов. Который тоже задействует планировщик.
Интересно, можно ли както синхронизировать потоки некоей атомарной операцией в usermode.
Притом так, чтобы пока поток спит, на его месте (в ядре CPU) исполнялись другие потоки.
Кстати, отдельный вопрос — не понятно, как потоки привязываются к ядрам? Привязываются ли.
Т.е в общем виде может ли заснувший поток — проснуться в другом ядре?
Здравствуйте, barney, Вы писали:
B>Интересно, можно ли както синхронизировать потоки некоей атомарной операцией в usermode.
В том и отличие mutex от spinlock, что первый может освободить процессор и для этого переходит в режим ядра сисколлом, а спинлок просто в user-level крутит атомарную проверку-и-захват флага, пока другой поток его не освободит. Если ожидание недолгое, то спинлок быстрее. Однако, если ожидание долгое, то лучше все-таки освободить процессор для других потоков.
Условная переменная работает тоже через sys_futex и переходит в режиме ядра для освобождения процессора, хотя ее тоже можно реализовать как спинлок на одних атомарных проверках в user-level.
B>Кстати, отдельный вопрос — не понятно, как потоки привязываются к ядрам? Привязываются ли. B>Т.е в общем виде может ли заснувший поток — проснуться в другом ядре?
В общем случае да, поток может проснуться на любом ядре.
Но если нужно оптимизировать исполнением кода на конкретном ядре для эффективного использования кэша этого ядра, то можно привязать поток к ядру — для этого есть специальные системные вызовы.
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)
lpd>В том и отличие mutex от spinlock, что первый может освободить процессор и для этого переходит в режим ядра сисколлом, а спинлок просто в user-level крутит атомарную проверку-и-захват флага, пока другой поток его не освободит. Если ожидание недолгое, то спинлок быстрее. Однако, если ожидание долгое, то лучше все-таки освободить процессор для других потоков.
т.е получается ядро просто исполняет некий набор инструкций (user mode) пока не прийдет системное прерывание таймера, или не встретится системный вызов.
Тогда управление перейдет в kernel. Т.е в общем случае, освободить ядро досрочно — такая же "по тяжеловесности" операция, как и истечение кванта времени, так?
Затем уже в ядре планировщик решает, на какой поток передать управление далее.
И "спящий" поток просто будет временно игнорироваться, т.о. исключаясь из планирования. Пока планировщику не просигналят — что он уже готов "проснуться"
В общем то, не так все и страшно, значит...
lpd>Условная переменная работает тоже через sys_futex и переходит в режиме ядра для освобождения процессора, хотя ее тоже можно реализовать как спинлок на одних атомарных проверках в user-level.
О, вот это интересно
А можно подробнее?
Как именно условная переменная реализована? Есть ли где то примеры, как это сделать через мьютекс
Я думал что condition variable и mutex это некие фундаментальные, не сводимые друг к другу классы, оказывается, первое реализуется через второе?
Здравствуйте, barney, Вы писали:
B>т.е получается ядро просто исполняет некий набор инструкций (user mode) пока не прийдет системное прерывание таймера, или не встретится системный вызов. B>Тогда управление перейдет в kernel. Т.е в общем случае, освободить ядро досрочно — такая же "по тяжеловесности" операция, как и истечение кванта времени, так? B>Затем уже в ядре планировщик решает, на какой поток передать управление далее. B>И "спящий" поток просто будет временно игнорироваться, т.о. исключаясь из планирования. Пока планировщику не просигналят — что он уже готов "проснуться" B>В общем то, не так все и страшно, значит...
То, что здесь писали про мьютекс и исполнение инструкций pause — это неправильно.
Для освобождения процессора нужно перейти в режим ядра и передать управление системному планировщику. Планировщик сохранит регистры потока в память, полностью прекратит его исполнение, и начнет исполнять на этом ядре какой-то другой поток другого процесса, или ядерный поток, если такие ждут квантов времени. В будущем планировщик может восстановить из памяти регистры усыпленного потока, в том числе переключить таблицу страниц, и вернуть его к исполнению.
Переход в режим ядра и тем более переключение контекста на другой поток — достаточно долгие операции.
B>Я думал что condition variable и mutex это некие фундаментальные, не сводимые друг к другу классы, оказывается, первое реализуется через второе?
Это разные операции. В теории одну можно свести к другой, но это не очень удобно.
И мьютекс, и условная переменная реализуются через системный вызов sys_futex().
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)
lpd>Переход в режим ядра и тем более переключение контекста на другой поток — достаточно долгие операции.
возникла идея безумная. вот смотрите, ведь все равно- потоки используют общую память. т.е им не нужен "защищенный режим" изоляции друг от друга
можно ли на уровне ОС сделать "user mode" потоки?
например юзермодовское прерывание таймера, которое не меняет контекст (user to kernel)?
т.о. потоки в рамках одного процесса могут быстро останавливаться — юзермод таймер прерыванием — быстро переключать стек регистров — и продолжать управление.
т.е если общий квант времени, отведенного на процесс не истек — то получим сверхшустрые потоки в рамках этого кванта
lpd>Это разные операции. В теории одну можно свести к другой, но это не очень удобно. lpd>И мьютекс, и условная переменная реализуются через системный вызов sys_futex().
Понятно. Я так понимаю это смотреть имплементацию POSIX cond_variable в ядре линукс?
lpd>Раньше лет 15 назад в Linux потоки так и были реализованы без sys_futex(). Детали не знаю, но для переключения исполнения между потоками можно использовать user-level libc-функции setjmp()/longjmp().
любопытно! покопаю в эти setjmp()/longjmp()
но, подозреваю, там опять же без прерывания текущего потока исполнения по таймеру
т.е нечто вроде корутин
Здравствуйте, barney, Вы писали:
B>но, подозреваю, там опять же без прерывания текущего потока исполнения по таймеру B>т.е нечто вроде корутин
Нет, там было имнно переключение по таймеру. По-идее, вместо системного прерывания таймера можно просто использовать системные вызовы асинхронных таймеров.
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)
B>имхо, тут нужно прерывание по таймеру, но не уводящее CPU в kernel mode
я и не сказал что соцпрограммы работают по таймеру
но примеры которые я +10500 раз привел, все объясняют как это сделано у других и размазано по ядрам цпу
Здравствуйте, barney, Вы писали:
B>но это приводит не просто к простою потока, а постоянной нагрузке
Есть два принципиально разных случая
1) ожидая освобождения, можно стать в очередь и ждать, пока кто-то не разбудит
2) будить некому, нет варианта, кроме как крутиться в цикле
Второй — это или межпроцессорные спинлоки за общий ресурс, или синхронизация между тредами/процессами/etc. при отсутствии толкового диспетчера.
Вариант "вместо кручения в цикле ждём на mwait" принципиально сути не меняет.
В первом, если не удалось захватить атомарным test-and-set или его аналогом (LL/SC на RISC), ставятся в очередь к диспетчеру, и тут коллеги уже расписали происходящее чуть более, чем во всех деталях.
B>А POSIX функция pthread_mutex_lock уже, я так понимаю, использует некие функции ядра? B>в LINUX это futex B>асли это DARWIN/MACH то есть некие ядерные mach_ функции для блокировки/просыпания потоков, которые завязаны на мьютекс
POSIX API реализован как часть системной Cи либы. Функции этой либы, в свою очередь, опираются на функционал, предоставляемый ядром через сисколы. Это сисколы, в конечном итоге, дёргаются системными либами во в сех ОСях, и в DARWIN/MATCH. Сисколы базируются в предоставлении функционала на подсистемах ядра. Так, сискол вроде futex, уходит корнями в подсистему управления тредами в ядре. Чтоб узнать как реализовано оно всё в ядре-читайте исходники.
N>В первом, если не удалось захватить атомарным test-and-set или его аналогом (LL/SC на RISC), ставятся в очередь к диспетчеру, и тут коллеги уже расписали происходящее чуть более, чем во всех деталях.
Т.е без вызова диспатчера (и соотв. ухода в kernel mode сисколом) отключить поток от исполнения — нет способов?
Здравствуйте, barney, Вы писали:
N>>В первом, если не удалось захватить атомарным test-and-set или его аналогом (LL/SC на RISC), ставятся в очередь к диспетчеру, и тут коллеги уже расписали происходящее чуть более, чем во всех деталях.
B>Т.е без вызова диспатчера (и соотв. ухода в kernel mode сисколом) отключить поток от исполнения — нет способов?
Здравствуйте, barney, Вы писали:
B>Интересно, как реализованы базовые примитивы мультипоточности в C++? Такие как mutex
B>насколько я понимаю, все базируется на атомарной test-and-set инструкции (которую я бы назвал fetch-and-set) B>и spin_lock блокировке, которая выглядит примерно так:
B>
B>spin_outer:
B> cmp flag, 1
B> je spin_outer // waiting until 0
B>spin_inner:
B> test-and-set ax, flag //executed atomicly
B> // {
B> // mov ax, flag // fetch current value to ax
B> // mov flag, 1 // set flag to 1
B> // }
B> cmp ax, 1
B> jne spin_inner // if old value was 0
В конце — важные выводы: — Код, ограниченный критическими секциями, лучше всего свести к минимуму.
— Находясь в критической секции, не стоит вызывать методы "чужих" объектов.
B>как же работает мютекс?
В отличие от критической секции, мьютекс — объект системного ядра (в том же Windows API).
Предположу, что там "ожидание" это не простой цикл в условным/переходом на метку, я передача управления стстемному диспетчеру потоков.
В этом случае, задача STL (std::mutex) будет сведена просто к вызову соответствующих системных методов WinAPI или Posix.
вот, сделал, учебную реализацию спин-блокировки через test-and-set для x64
На мак оси работает с ошибками,
в общем виде вывод смешан (как и должно быть, тк main thread не блочится)
но даже между обоими spinner почему то такая блокировка не работает:
"INSIDE THE LOCK" выводится не всегда неразрывно со spin_count
хотя здесь должна быть жесткая гарантия
Спасибо за ссылочки, любопытно
AG>В конце — важные выводы: AG> AG>- Код, ограниченный критическими секциями, лучше всего свести к минимуму. AG>- Находясь в критической секции, не стоит вызывать методы "чужих" объектов.
Воот...
Вообще бы, хотелось программировать в какой то иной парадигме,
без критических секций и мьютексов.
Мне нравится идея максимально заполнять ядра процессора работой,
с помощью work queue или thread pool
Увы, они реализованы внутри тоже с применением cond_variable и эти потоки "засыпают" через вызов функций ядра.
Т.к кусочки работы могут быть вполне небольшими — например обновить значение текстовой строки в UI по возвращению данных из сети и тд.
Впрочем, если бы можно было переключать потоки из user mode легковесным образом, (тут уже обсуждали выше user mode таймер)
была бы чистая шустрая и элегантная система воркеров!
CF=0 как раз если было 0, то есть спинлок захвачен. Нафига в этом случае возвращаться на loop? Ты делаешь всё наоборот — если захватил, идёшь на круг
Тут лучше ложится jc outer (и вообще, outer и loop совместить)
B> mov spins_count,ecx B> } B> // locked B> std::cout << "\nINSIDE_THE_LOCK:" << spins_count << "\n"; B> __asm { B> mov rbx,flag B> xor eax,eax B> mov [rbx],eax
Вот уж где ассемблер нафиг не нужен, достаточно простого присвоения... хотя, если компилятор системы MSVC или близкий, лучше так.
Здравствуйте, barney, Вы писали:
B>Мне нравится идея максимально заполнять ядра процессора работой, B>с помощью work queue или thread pool B>Увы, они реализованы внутри тоже с применением cond_variable и эти потоки "засыпают" через вызов функций ядра.
Только когда нет работы. Когда она есть — им незачем спать.
Короткий момент в mutex_lock, когда другие воркеры разбираются с задачами и их отсутствием, не считаем.
B>Т.к кусочки работы могут быть вполне небольшими — например обновить значение текстовой строки в UI по возвращению данных из сети и тд. B>Впрочем, если бы можно было переключать потоки из user mode легковесным образом, (тут уже обсуждали выше user mode таймер) B>была бы чистая шустрая и элегантная система воркеров!
Здравствуйте, barney, Вы писали:
B>Вот "INSIDE_THE_LOCK:SPINNER:•2W•" B>здесь должно быть "INSIDE_THE_LOCK:2" или "INSIDE_THE_LOCK: ••2" но SPINNER должен был вывестись ПОСЛЕ
Лучше пиши в std::cerr, т.к. std::cout вообще буферизуется, хотя отладить так все равно нельзя.
Для отладки spinlock можно сделать глобальную переменную int counter, и инкрементировать ее заданное число раз из разных потоков.
Без синхронизации или с неправильным мьютексом значение counter в конце почти всегда будет меньше, чем должно.
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)
Здравствуйте, netch80, Вы писали:
N>1. Что за компилятор?
Apple LLVM version 9.1.0
N>2. Явная ошибка:
B>> __asm { B>> outer: B>> mov rbx,flag B>> mov eax,[rbx] B>> cmp eax,0 B>> jne outer
B>> xor ecx,ecx B>> xor eax, eax B>> mov rbx,flag B>> loop: B>> inc ecx B>> lock bts [rbx], eax // lock + bts atomic test-and-set B>> jnc loop
N>CF=0 как раз если было 0, то есть спинлок захвачен. Нафига в этом случае возвращаться на loop? Ты делаешь всё наоборот — если захватил, идёшь на круг N>Тут лучше ложится jc outer (и вообще, outer и loop совместить)
ммм... моя логика такая:
если мьютекс еще не захвачен ( == 0) то вызов 'bts [rbx], eax' в керри положит старое значени 0, и атомарно в память 1
(но, до этого момента, другой поток УЖЕ мог прочитать 0 в таком же outer цикле, и перейти на inner этап)
теперь проц дает гарантию что lock bts атомарно положит там 1 хотя бы в одном из потоков!
т.е у кого то carry будет == 0 а у кого == 1
вот у того, кого 0 — тот захватил первым
Здравствуйте, barney, Вы писали:
B>Вообще бы, хотелось программировать в какой то иной парадигме, B>без критических секций и мьютексов.
Всё зависит от задач.
Я обычно мьютексы не использую, так как это "тяжёлые" системные объекты.
Насчет критических секций — не вижу ничего предосудительного в их применении.
Часто я аккуратно пользуюсь ими. Здесь важно — НЕ вводить в облась, защищенную секцией много кода:
::EnterCriticalSection(m_pCS);
// Самые элементарные действия с общими ресурсами
::LeaveCriticalSection(m_pCS);
Заполнять ядра CPU работой — это палка о двух концах:
При работе таких приложений, компьютер очень медленно реагирует на действия пользователя.
Правильнее — где-то в циклах рабочих потоков вводить ::Sleep(100...500);
— чтобы отдать управление системе (исключить жуткие тормоза для end-user-а).
Здравствуйте, 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.
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 — не надо)! Кого бог хочет наказать, лишает разума...
Здравствуйте, 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.
Во-вторых, на однопроцессорной машине крутиться в спин-цикле бессмысленно, так как лок никто в
это время не освободит.
Ну и в-третьих, если между захватом и освобождением лока поток будет вытеснен, получится
очень некрасивая ситуация по отношению к другим потокам, которые ждут его освобождения.
Особенно если лок не гарантирует порядок захвата. Так легко и систему завесить, особенно если
такой спинлок применять где-нибудь в критических местах — драйверы, всякие системные
обработчики и т.д. Встречал на практике.
Здравствуйте, andrey.desman, Вы писали:
AD>Нет, конечно, не утверждал, но учитывая вышенаписанное, ты скорее сам исходники смотрел не шибко внимательно.
Да, просмотрел. В случае если, таки, необходимо засыпать, используется WaitForSingleObjectEx и поток уходит в ядро. Вся остальная реализация в user-mode. Т.е. все достаточно не плохо под Винду . Некоторые другие вещи в CRT меня ужасали, бывало.
Здравствуйте, okman, Вы писали:
O>Здравствуйте, AlexGin, Вы писали:
AG>>while(lck.test_and_set(std::memory_order_acquire)) [b]// здесь, возможно, придется подождать!
O>Спинлок в такой имплементации — это вообще, как по мне, плохая идея.
Вот, я тоже об этом подумал. Т.к если есть сначала дешевый test в цикле, то шину и кеш не грузим,
а затем по факту освообждения переходим к шагу два с попыткой атомарно захватить. Если не успели — возвращаемся на дешевый цикл чтения.
O>Во-первых, постоянно теребить глобальную переменную с xchg/cmpxchg и блокировкой шины — не есть гуд O>(bus traffic и все такое). Как минимум, тут надо применять стратегию не "test and set", а O>"test test and set" + какую-то разгрузку в виде yield/pause/Sleep/etc.
А такое можно реализовать легковесно в user mode не отдавая контекст ядру/планировщику?
O>Во-вторых, на однопроцессорной машине крутиться в спин-цикле бессмысленно, так как лок никто в O>это время не освободит.
O>Ну и в-третьих, если между захватом и освобождением лока поток будет вытеснен, получится O>очень некрасивая ситуация по отношению к другим потокам, которые ждут его освобождения. O>Особенно если лок не гарантирует порядок захвата. Так легко и систему завесить, особенно если O>такой спинлок применять где-нибудь в критических местах — драйверы, всякие системные O>обработчики и т.д. Встречал на практике.
Любопытный вопрос. А какие практики используются вместо этого в ядре?
AG>Взято отсюда: AG>http://anki3d.org/spinlock
AG>Теперь предположим, что в результате некоторой ситуации все ядра моего CPU ждут на while в методе lock. AG>Да, случай практически не вероятный, но всё-таки — полностью НЕ исключаемый. AG>Загрузка всех секций CPU будет под 100% (здесь я даже не спрашиваю насчет полезноти/бесполезности данной вычислительной работы). AG>Спрошу только одно — сможет ли пользователь хоть как-то взаимодействовать с ОС? Хоть бы для того, чтобы снять эту зависшую задачу?
Ну а зачем так делать? Вот вам принципы из практики на счет производительности, чтобы с многопоточностью все было ок:
1. Как не странно. не разделяйте данные. Если можно, всегда делайте локальные копии данных для каждого потока.
2. Старайтесь все данные через блокировки передавать пакетировано и как можно быстрее. В идеале, все должно сводится к обмену указателей на массив объектов.
3. Spin-Lock дает выигрыш только если количество итераций достаточно мало. В противном случае выгоднее уйти в ядро и не тратить циклы CPU.
Здравствуйте, barney, Вы писали:
B>А такое можно реализовать легковесно в user mode не отдавая контекст ядру/планировщику?
Наверное, ничего лучше инструкции pause пока не придумали.
B>Любопытный вопрос. А какие практики используются вместо этого в ядре?
Конкретно ядро Windows перед захватом спинлока задирает IRQL до DISPATCH_LEVEL, это предотвращает вытеснение
потока планировщиком (хотя это не исключает, например, приход прерывания, в результате чего код все равно
будет временно вытеснен). Ну и вместо обычных спинлоков рекомендуется применять спинлоки с очередями на
стеке (in-stack queued spinlock) — там каждый поток вместо глобальной переменной теребит переменную у
себя в стеке, ну и гарантируется порядок захвата спинлока потоками (т.е. убирается проблема "starvation").
Про linux не в курсе, я его видел только "на картинках"
В режиме пользователя запретить вытеснение потока нельзя. Думаю, в режиме пользователя по этой
причине никаких спинлоков быть не должно.
Здравствуйте, AlexGin, Вы писали:
N>>Уточните, под какой ОС такое происходит. AG>Последний раз видел такую ситуацию — примерно пять лет назад под Windows XP.
Хммм...
N>>Честно, у меня как-то не наблюдалось. Но у меня уже лет 20 что сервера, что десктопы/лаптопы почти сплошь Linux и FreeBSD.
AG>А разве в мире Linux что-то принцыпиально иначе? Разве не будет такой же ситуации? AG>Если на каждом из ядер крутится свой поток, и не даёт возможности "вклиниться" диспетчеру потоков, то там должно быть аналогично.
С чего это вдруг "должно" быть?
Регулярно приходят таймерные прерывания (или при NOHZ режиме их можно заказывать на конкретные моменты).
Диспетчер отслеживает потребление процессора нитями и делает, чтобы каждая нить из желающих получала долю процессора, максимально близко пропорциональную некоторому числу, зависящему от приоритета.
Кроме этого, есть система временного буста диспетчерского приоритета для нитей, которые до того находились в спячке достаточное время — это помогает типовым интерактивным задачам, которые реагируют на события от пользователя и спят в остальное время; это делается на каком-то варианте token bucket.
Если нить сама не отдаёт процессор, то её по таймерному прерыванию или даже по любому сисколлу могут вытеснить принудительно. Диспетчер всегда имеет право вклиниться и переключить активную нить на любом ядре, кроме сверхкоротких периодов пребывания в kernel land между точками выхода в user land, ожиданий или явных yield'ов.
И мне просто дико слышать, что где-то иначе, тем более что "должно" быть иначе — это ж какой, извините, каменный век в алгоритмах?
(Нет, конечно, не всё так однозначно, я сама дочь офицера есть realtime priority и ещё хитрые фишки того же плана — но вряд ли какой безумец сделает на них thread pool. И, наоборот, есть шейперы, которые позволяют урезать права)
AG>Предположим, что у нас обычный офисный комп с Win10/Win7. AG>У меня есть приложение, которое (выполняя определенную работу) — дико тормозит все мои пользовательские действия. AG>Что я могу сделать, как пользователь? Что могу сделать, если приложение сторонее и я НЕ имею его исходных кодов?
Вот был бы Linux — я бы сказал, как сделать, чтобы это приложение не брало более, например, 40% CPU, если есть другие желающие — конкретно, через cgroup/cpu.shares. А с Windows... боюсь, что тут ровно как в знаменитом лозунге про спасение утопающих...
Здравствуйте, reversecode, Вы писали:
R>коллективный разум это когда решается какая то проблема доселе не известная или не имеющая решения R>а как устроены ОС описано в гугле на каждом шагу, плюс куча книг и исходников R>это называется по другому, — научите меня бесплатно
А сам то ты здесь наверно ошиваешься не для того чтобы учить/учиться, а флеймить ради?
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Здравствуйте, okman, Вы писали:
O>Спинлок в такой имплементации — это вообще, как по мне, плохая идея.
Если этим спинлоком защищать только условные Get/Set для некоторого (общего для потоков) значения, то идея вроде как хорошая.
Но, конечно же, при использовании его есть риск завесить приложение, если вынесли в него некие сложные действия.
O>Во-первых, постоянно теребить глобальную переменную с xchg/cmpxchg и блокировкой шины — не есть гуд O>(bus traffic и все такое). Как минимум, тут надо применять стратегию не "test and set", а O>"test test and set" + какую-то разгрузку в виде yield/pause/Sleep/etc.
+100500
Вот именно о разгрузке в виде ::Sleep — я и сообщал здесь выше!
В виде Sleep или WaitingFor... ввести ожидание, чтобы передать управление диспетчеру потоков.
O>Во-вторых, на однопроцессорной машине крутиться в спин-цикле бессмысленно, так как лок никто в O>это время не освободит.
Да, однопроцессорных сейчас уже давно нет, но когда-то (лет 15 назад) как-то же работала многопоточка?
O>Ну и в-третьих, если между захватом и освобождением лока поток будет вытеснен, получится O>очень некрасивая ситуация по отношению к другим потокам, которые ждут его освобождения.
Вероятность того, что поток будет вытеснен, если внутри этого лока — только один вызов гетера/сетера — фактически нулевая.
O>Особенно если лок не гарантирует порядок захвата. Так легко и систему завесить, особенно если O>такой спинлок применять где-нибудь в критических местах — драйверы, всякие системные O>обработчики и т.д. Встречал на практике.
+100500
Да, если внутрь этого лока вносить что-то длинное, то такое бывает. Я когда-то также сталкивался с этим.
Но здесь — вся ответственность на разработчике
Каких-либо механизмов, способных разрулить проблему IMHO не существует.
Кроме, разве что сделать ожидание в локе — с прокачкой сообщений ОС, и с вызовом Sleep...
Здравствуйте, Videoman, Вы писали:
AG>>Теперь предположим, что в результате некоторой ситуации все ядра моего CPU ждут на while в методе lock. AG>>Да, случай практически не вероятный, но всё-таки — полностью НЕ исключаемый. AG>>Загрузка всех секций CPU будет под 100% (здесь я даже не спрашиваю насчет полезноти/бесполезности данной вычислительной работы). AG>>Спрошу только одно — сможет ли пользователь хоть как-то взаимодействовать с ОС? Хоть бы для того, чтобы снять эту зависшую задачу?
V>Ну а зачем так делать? Вот вам принципы из практики на счет производительности, чтобы с многопоточностью все было ок: V>1. Как не странно. не разделяйте данные. Если можно, всегда делайте локальные копии данных для каждого потока.
Есть немалая вероятность, что когда-либо эти данные окажутся рассинхронизированы (будут иметь различные значения, вместо одного).
V>2. Старайтесь все данные через блокировки передавать пакетировано и как можно быстрее. В идеале, все должно сводится к обмену указателей на массив объектов.
+100500
Это бесспорно так.
V>3. Spin-Lock дает выигрыш только если количество итераций достаточно мало. В противном случае выгоднее уйти в ядро и не тратить циклы CPU.
+100500
Здесь также согласен.
Здравствуйте, lpd, Вы писали:
lpd>Здравствуйте, AlexGin, Вы писали:
AG>Да, но есть риск получить систему полностью загруженной, когда она перестанет отзываться на действия пользователя. AG>Вполне возможно, что в цикле рабочего потока ::Sleep(100) — не вызовет катастрофических задержек. AG>Однако, вызвав диспетчер потоков позволит пользователю как-то общаться с машиной, когда запущена твоя вычислительная задача. AG>В противном случае — компьютер становиться страшно тормознутым и практически не управляемым
lpd>Лучше уменьшить приоритет вычислительной задаче через системный менеджер процессов, например, или из кода.
Так получится, что уменьшение приоритета даст примерно тот же эффект (если не хуже),
в плане снижения производительности, что тот же самый ::Sleep
Здравствуйте, AlexGin, Вы писали:
AG>Вероятность того, что поток будет вытеснен, если внутри этого лока — только один вызов гетера/сетера — фактически нулевая.
Смысл в том, что она все равно не нулевая.
У нас в софте были такие зависания, оказалось там была схожая реализация лока с циклом на CMPXCHG.
Код под локом — всего несколько процессорных инструкций, но все равно иногда случалось, что
он вытеснялся и система висла (потому что за лок конкурировали другие важные потоки системы).
Когда переделали на стандартные виндовые spinlock/pushlock — все заработало.
AG>Каких-либо механизмов, способных разрулить проблему IMHO не существует. AG>Кроме, разве что сделать ожидание в локе — с прокачкой сообщений ОС, и с вызовом Sleep...
Ну в ядре, как я уже писал, можно на время запретить переключение контекстов, ядерные
спинлоки так и работают (повышая IRQL до соответствующего уровня, где шедулер не работает).