Re[2]: внутренняя реализация std::mutex?
От: andrey.desman  
Дата: 17.05.18 09:52
Оценка: +3
Здравствуйте, Videoman, Вы писали:

V>Под Windows, например (VS2013), std::mutex реализован без использования вызовов WinAPI, почти так, как вы и описали, но spin блокировка крутится заданное число циклов (сейчас 4000) и уже по истечении этого времени квант потока отдается (он засыпает на pause). Фактически в С++ рантайм вынесены "кишки" стандартной critical section.



Серьезно? А как же очередь ожидающих и приоритизация? То есть, какой-то один поток может бесконечно долго висеть и не захватить мьютекс?
Re[2]: внутренняя реализация std::mutex?
От: reversecode google
Дата: 17.05.18 09:57
Оценка: +2 -1
Здравствуйте, barney, Вы писали:

B>вот, сделал, учебную реализацию спин-блокировки через test-and-set для x64


атомики в С++ уже давно ввели
вы с какого года к нам с асмом пришли ? переставте число обратно на 2003 на своей машине времени
Re[5]: внутренняя реализация std::mutex?
От: reversecode google
Дата: 17.05.18 06:35
Оценка: +1 -1
коллективный разум это когда решается какая то проблема доселе не известная или не имеющая решения
а как устроены ОС описано в гугле на каждом шагу, плюс куча книг и исходников
это называется по другому, — научите меня бесплатно

это все равно что если бы я создал тему "как устроен С++ ?"
и объясняйте мне на пальцах все о нём

ну т.е. ваша тема флеймовая, — сам читать не хочу, объясняйте мне своими словами
Re[5]: внутренняя реализация std::mutex?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 17.05.18 12:32
Оценка: +2
Здравствуйте, AlexGin, Вы писали:

AG>Здравствуйте, netch80, Вы писали:


N>>Только когда нет работы. Когда она есть — им незачем спать.

AG>
AG>Да, но есть риск получить систему полностью загруженной, когда она перестанет отзываться на действия пользователя.

Не знаю, при каком шедулере такое возможно.
В нормальном, пусть даже при N ядрах у нас N рабочих тредов в пуле, задача реакции на пользователя всё равно получит свои кванты, хоть и заметно меньше.
Если же сделать N-1 рабочий тред, то должно хватить места на заметную активность всех других видов.
(Считаю, что других задач на машине не крутится. А вообще для таких вещей надо делать конфигурируемый параметр.)

AG>Вполне возможно, что в цикле рабочего потока ::Sleep(100) — не вызовет катастрофических задержек.

AG>Однако, вызвав диспетчер потоков позволит пользователю как-то общаться с машиной, когда запущена твоя вычислительная задача.
AG>В противном случае — компьютер становиться страшно тормознутым и практически не управляемым

Уточните, под какой ОС такое происходит. Честно, у меня как-то не наблюдалось. Но у меня уже лет 20 что сервера, что десктопы/лаптопы почти сплошь Linux и FreeBSD.

Но в общем случае я поправку принимаю, с нормальными шедулерами она не должна сильно помешать, а с кривыми — поверю, что помогает.
The God is real, unless declared integer.
Re[2]: внутренняя реализация std::mutex?
От: okman Беларусь https://searchinform.ru/
Дата: 18.05.18 16:09
Оценка: 3 (1)
Здравствуйте, barney, Вы писали:

B>Нашел любопытный разбор легковесных мьютексов под win32


B>http://preshing.com/20120226/roll-your-own-lightweight-mutex/


На Windows Vista и выше есть вот это:

Slim Reader/Writer (SRW) Locks
https://msdn.microsoft.com/en-us/library/windows/desktop/aa904937(v=vs.85).aspx

Это легковесный аналог mutex/крит.секции, но поддерживающий семантику 'читатели-писатель'.
Re: внутренняя реализация std::mutex?
От: lpd Черногория  
Дата: 16.05.18 18:21
Оценка: +1
Здравствуйте, barney, Вы писали:

B>Интересно, как реализованы базовые примитивы мультипоточности в C++? Такие как mutex


B>но это приводит не просто к простою потока, а постоянной нагрузке


B>при использовании std::mutex для критической секции такого не наблюдается.


B>как же работает мютекс?


В linux mutex работает через системный вызов futex, который освобождает процессор и добавляет поток в очередь, если мьютекс занят.
Документация: manual
Можно почитать здесь: статья
Также есть комментарии в коде ядра futex.c
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)
Re: внутренняя реализация std::mutex?
От: smeeld  
Дата: 16.05.18 21:19
Оценка: +1
Здравствуйте, barney, Вы писали:

B>Интересно, как реализованы базовые примитивы мультипоточности в C++? Такие как mutex


В разных реализациях STL и для разных ОС по-разному. Например, в GCC для unix-like-ов std::mutex-это приплюснутый врайпер над POSIX API.

  void
       lock()
       {
         int __e = __gthread_mutex_lock(&_M_mutex);
   
         // EINVAL, EAGAIN, EBUSY, EINVAL, EDEADLK(may)
         if (__e)
           __throw_system_error(__e);
       }


 static inline int
 __gthread_mutex_lock (__gthread_mutex_t *mutex)
 {
   if (__gthread_active_p ())
     return __gthrw_(pthread_mutex_lock) (mutex);
   else
     return 0;
 }
Re[4]: внутренняя реализация std::mutex?
От: barney  
Дата: 17.05.18 05:26
Оценка: -1
R>какой смысл их исследовать ?
R>в юниксе код открыт, хотите открывайте и читайте
R>в винде WRK тоже открыт + windows internal pdf есть

Не исследуйте, кто ж вас заставляет)
Мне интереснее человеческое общение на данные темы.
Например, книге нельзя задать вопрос по мере его возникновения.
Поэтому лично мне интереснее исследовать тему коллективным разумом.
Re[5]: внутренняя реализация std::mutex?
От: lpd Черногория  
Дата: 17.05.18 06:15
Оценка: +1
Здравствуйте, 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, и если успеет атомарно захватит его.
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)
Отредактировано 17.05.2018 6:19 lpd . Предыдущая версия . Еще …
Отредактировано 17.05.2018 6:17 lpd . Предыдущая версия .
Re[11]: внутренняя реализация std::mutex?
От: reversecode google
Дата: 17.05.18 08:29
Оценка: +1
B>возникла идея безумная. вот смотрите, ведь все равно- потоки используют общую память. т.е им не нужен "защищенный режим" изоляции друг от друга
B>можно ли на уровне ОС сделать "user mode" потоки?
B>например юзермодовское прерывание таймера, которое не меняет контекст (user to kernel)?
B>т.о. потоки в рамках одного процесса могут быстро останавливаться — юзермод таймер прерыванием — быстро переключать стек регистров — и продолжать управление.
B>т.е если общий квант времени, отведенного на процесс не истек — то получим сверхшустрые потоки в рамках этого кванта

это все от того что вы не хотите почитать все что уже придумано до вас фибры в винде https://msdn.microsoft.com/en-us/library/windows/desktop/ms682661(v=vs.85).aspx
curoutines в обеих ОС
ну и http://rsdn.org/forum/cpp/7147121.1
Автор: reversecode
Дата: 17.05.18
Re[11]: внутренняя реализация std::mutex?
От: lpd Черногория  
Дата: 17.05.18 08:30
Оценка: -1
Здравствуйте, barney, Вы писали:

lpd>>Переход в режим ядра и тем более переключение контекста на другой поток — достаточно долгие операции.


B>возникла идея безумная. вот смотрите, ведь все равно- потоки используют общую память. т.е им не нужен "защищенный режим" изоляции друг от друга

B>можно ли на уровне ОС сделать "user mode" потоки?
Раньше лет 15 назад в Linux потоки так и были реализованы без sys_futex(). Детали не знаю, но для переключения исполнения между потоками можно использовать user-level libc-функции setjmp()/longjmp().
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)
Отредактировано 17.05.2018 8:33 lpd . Предыдущая версия .
Re[4]: внутренняя реализация std::mutex?
От: AlexGin Беларусь  
Дата: 17.05.18 10:22
Оценка: -1
Здравствуйте, netch80, Вы писали:

N>Только когда нет работы. Когда она есть — им незачем спать.


Да, но есть риск получить систему полностью загруженной, когда она перестанет отзываться на действия пользователя.
Вполне возможно, что в цикле рабочего потока ::Sleep(100) — не вызовет катастрофических задержек.
Однако, вызвав диспетчер потоков позволит пользователю как-то общаться с машиной, когда запущена твоя вычислительная задача.
В противном случае — компьютер становиться страшно тормознутым и практически не управляемым
Re[5]: внутренняя реализация std::mutex?
От: lpd Черногория  
Дата: 17.05.18 13:00
Оценка: +1
Здравствуйте, AlexGin, Вы писали:

AG>Да, но есть риск получить систему полностью загруженной, когда она перестанет отзываться на действия пользователя.

AG>Вполне возможно, что в цикле рабочего потока ::Sleep(100) — не вызовет катастрофических задержек.
AG>Однако, вызвав диспетчер потоков позволит пользователю как-то общаться с машиной, когда запущена твоя вычислительная задача.
AG>В противном случае — компьютер становиться страшно тормознутым и практически не управляемым

Лучше уменьшить приоритет вычислительной задаче через системный менеджер процессов, например, или из кода.
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)
Отредактировано 17.05.2018 13:01 lpd . Предыдущая версия .
внутренняя реализация std::mutex?
От: barney  
Дата: 16.05.18 18:12
Оценка:
Интересно, как реализованы базовые примитивы мультипоточности в 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 для критической секции такого не наблюдается.

как же работает мютекс?
Отредактировано 16.05.2018 18:13 barney . Предыдущая версия .
Re: внутренняя реализация std::mutex?
От: reversecode google
Дата: 16.05.18 18:33
Оценка:
что значит mutex и std::mutex ?
называйте точно о чем говорите
Re: внутренняя реализация std::mutex?
От: Videoman Россия https://hts.tv/
Дата: 16.05.18 20:54
Оценка:
Здравствуйте, barney, Вы писали:

B>Интересно, как реализованы базовые примитивы мультипоточности в C++? Такие как mutex

Конкретная реализация целиком зависит от рантайма и на разных платформах реализована по разному.

B>это приводит не просто к простою потока, а постоянной нагрузке

B>при использовании std::mutex для критической секции такого не наблюдается.

Под Windows, например (VS2013), std::mutex реализован без использования вызовов WinAPI, почти так, как вы и описали, но spin блокировка крутится заданное число циклов (сейчас 4000) и уже по истечении этого времени квант потока отдается (он засыпает на pause). Фактически в С++ рантайм вынесены "кишки" стандартной critical section.
Re[2]: внутренняя реализация std::mutex?
От: barney  
Дата: 16.05.18 21:11
Оценка:
V>Конкретная реализация целиком зависит от рантайма и на разных платформах реализована по разному.

Вот это и хочется изучить. Т.к не понятно, как именно мьютекс "усыпляет" поток, и кто потом его "просыпает"
как вообще работает эта машинерия.
Собственно для этого и создал данный топик
для тех кому интересно было бы исследовать и обсудить реализации для различных ОС / архитектур

V>Под Windows, например (VS2013), std::mutex реализован без использования вызовов WinAPI, почти так, как вы и описали, но spin блокировка крутится заданное число циклов (сейчас 4000) и уже по истечении этого времени квант потока отдается (он засыпает на pause). Фактически в С++ рантайм вынесены "кишки" стандартной critical section.


Понятно, любопытно. Т.е засыпание потока это уже функция ядра, и ядро же умеет "пробуждать" его, если где то в другом потоке мьютекс освободился?
т.е. ядро каким то образом связывает эти события
Re[3]: внутренняя реализация std::mutex?
От: reversecode google
Дата: 16.05.18 21:19
Оценка:
какой смысл их исследовать ?
в юниксе код открыт, хотите открывайте и читайте
в винде WRK тоже открыт + windows internal pdf есть

как устроены потоки, как устроены мютексы, что они куда переключают, все есть
Re[3]: внутренняя реализация std::mutex?
От: lpd Черногория  
Дата: 16.05.18 21:48
Оценка:
Здравствуйте, barney, Вы писали:

V>>Конкретная реализация целиком зависит от рантайма и на разных платформах реализована по разному.


B>Вот это и хочется изучить. Т.к не понятно, как именно мьютекс "усыпляет" поток, и кто потом его "просыпает"

B>как вообще работает эта машинерия.

Системный вызов futex работает в ядре, и усыпляет поток передачей управления функции schedule() планировщика процессов. Вообще планировщик вызывается или напрямую если процесс сам хочет освободить процессор, или из прерывания таймера. В зависимости от приоритетов процессов планировщик выбирает тот, которому положен следующий квант времени, и переключает на него контекст переключением таблицы страниц и загрузкой сохраненных значений регистров этого процесса.
Когда мьютекс освобождается, код futex помечает один из ждущих в очереди фьютекса потоков для исполнения, и планировщик при случае передаст ему управление.
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)
Отредактировано 16.05.2018 21:58 lpd . Предыдущая версия . Еще …
Отредактировано 16.05.2018 21:51 lpd . Предыдущая версия .
Re[4]: внутренняя реализация std::mutex?
От: barney  
Дата: 17.05.18 05:22
Оценка:
lpd>Системный вызов futex работает в ядре, и усыпляет поток передачей управления функции schedule() планировщика процессов. Вообще планировщик вызывается или напрямую если процесс сам хочет освободить процессор, или из прерывания таймера. В зависимости от приоритетов процессов планировщик выбирает тот, которому положен следующий квант времени, и переключает на него контекст переключением таблицы страниц и загрузкой сохраненных значений регистров этого процесса.

Ага, schedule() вызывается и по прерыванию таймером работающего потока, и в futex, так?
Т.е должен быть механизм, который отличает поток, ждущий futex от работающего потока, квант времени которого истек.
Иначе нет строгих гарантий что futex поток не проснется "внезапно" если планировщику вдруг так вздумается.

lpd>Когда мьютекс освобождается, код futex помечает один из ждущих в очереди фьютекса потоков для исполнения, и планировщик при случае передаст ему управление.


Т.е есть какой то системный condition variable в ядре, по которому поток "вешается" спать, до наступления сигнала ядра,
которое, например возникает когда другой поток освобождает тот же мьютекс?
Re[2]: внутренняя реализация std::mutex?
От: barney  
Дата: 17.05.18 05:24
Оценка:
Здравствуйте, smeeld, Вы писали:

S>В разных реализациях STL и для разных ОС по-разному. Например, в GCC для unix-like-ов std::mutex-это приплюснутый врайпер над POSIX API.


А POSIX функция pthread_mutex_lock уже, я так понимаю, использует некие функции ядра?
в LINUX это futex
асли это DARWIN/MACH то есть некие ядерные mach_ функции для блокировки/просыпания потоков, которые завязаны на мьютекс
Re[5]: внутренняя реализация std::mutex?
От: Videoman Россия https://hts.tv/
Дата: 17.05.18 06:03
Оценка:
Здравствуйте, barney, Вы писали:

B>Мне интереснее человеческое общение на данные темы.

B>Например, книге нельзя задать вопрос по мере его возникновения.
B>Поэтому лично мне интереснее исследовать тему коллективным разумом.

Дело в том, что в зависимости, даже, от версии студии и стандарта С++ реализация может сильно наворочена для поддержки всех примитивов. Но в итоге, все сводится к старому доброму test_and_set. Под Windows вы также можете посмотреть реализацию в исходниках CRT. Microsoft вынесла всю реализацию в user mode. В случае необходимости усыпить поток, что бы он не тратил ресурсы процессора, используется ассемблерная вставка, вызывающая инструкцию pause, которая специально предназначена для ожидания внутри spin lock.
Re[6]: внутренняя реализация std::mutex?
От: barney  
Дата: 17.05.18 07:21
Оценка:
R>коллективный разум это когда решается какая то проблема доселе не известная или не имеющая решения
R>а как устроены ОС описано в гугле на каждом шагу, плюс куча книг и исходников
R>это называется по другому, — научите меня бесплатно

феерическая чепуха)) про учебные группы слышал? добровольные, заметь
прийди еще в школу, поругай там разборы сортировки пузырьком)
а просветленные гуру — пусть идут мимо,
никто ж не претендует на их "небесплатное" внимание, лол)
Отредактировано 17.05.2018 7:32 barney . Предыдущая версия .
Re[6]: внутренняя реализация std::mutex?
От: barney  
Дата: 17.05.18 07:47
Оценка:
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) исполнялись другие потоки.

Кстати, отдельный вопрос — не понятно, как потоки привязываются к ядрам? Привязываются ли.
Т.е в общем виде может ли заснувший поток — проснуться в другом ядре?
Re[7]: внутренняя реализация std::mutex?
От: lpd Черногория  
Дата: 17.05.18 07:54
Оценка:
Здравствуйте, barney, Вы писали:

B>Интересно, можно ли както синхронизировать потоки некоей атомарной операцией в usermode.


В том и отличие mutex от spinlock, что первый может освободить процессор и для этого переходит в режим ядра сисколлом, а спинлок просто в user-level крутит атомарную проверку-и-захват флага, пока другой поток его не освободит. Если ожидание недолгое, то спинлок быстрее. Однако, если ожидание долгое, то лучше все-таки освободить процессор для других потоков.
Условная переменная работает тоже через sys_futex и переходит в режиме ядра для освобождения процессора, хотя ее тоже можно реализовать как спинлок на одних атомарных проверках в user-level.

B>Кстати, отдельный вопрос — не понятно, как потоки привязываются к ядрам? Привязываются ли.

B>Т.е в общем виде может ли заснувший поток — проснуться в другом ядре?

В общем случае да, поток может проснуться на любом ядре.
Но если нужно оптимизировать исполнением кода на конкретном ядре для эффективного использования кэша этого ядра, то можно привязать поток к ядру — для этого есть специальные системные вызовы.
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)
Отредактировано 17.05.2018 7:56 lpd . Предыдущая версия .
Re[8]: внутренняя реализация std::mutex?
От: barney  
Дата: 17.05.18 08:08
Оценка:
lpd>В том и отличие mutex от spinlock, что первый может освободить процессор и для этого переходит в режим ядра сисколлом, а спинлок просто в user-level крутит атомарную проверку-и-захват флага, пока другой поток его не освободит. Если ожидание недолгое, то спинлок быстрее. Однако, если ожидание долгое, то лучше все-таки освободить процессор для других потоков.

т.е получается ядро просто исполняет некий набор инструкций (user mode) пока не прийдет системное прерывание таймера, или не встретится системный вызов.
Тогда управление перейдет в kernel. Т.е в общем случае, освободить ядро досрочно — такая же "по тяжеловесности" операция, как и истечение кванта времени, так?
Затем уже в ядре планировщик решает, на какой поток передать управление далее.
И "спящий" поток просто будет временно игнорироваться, т.о. исключаясь из планирования. Пока планировщику не просигналят — что он уже готов "проснуться"
В общем то, не так все и страшно, значит...

lpd>Условная переменная работает тоже через sys_futex и переходит в режиме ядра для освобождения процессора, хотя ее тоже можно реализовать как спинлок на одних атомарных проверках в user-level.


О, вот это интересно
А можно подробнее?
Как именно условная переменная реализована? Есть ли где то примеры, как это сделать через мьютекс
Я думал что condition variable и mutex это некие фундаментальные, не сводимые друг к другу классы, оказывается, первое реализуется через второе?
Re[9]: внутренняя реализация std::mutex?
От: lpd Черногория  
Дата: 17.05.18 08:15
Оценка:
Здравствуйте, barney, Вы писали:

B>т.е получается ядро просто исполняет некий набор инструкций (user mode) пока не прийдет системное прерывание таймера, или не встретится системный вызов.

B>Тогда управление перейдет в kernel. Т.е в общем случае, освободить ядро досрочно — такая же "по тяжеловесности" операция, как и истечение кванта времени, так?
B>Затем уже в ядре планировщик решает, на какой поток передать управление далее.
B>И "спящий" поток просто будет временно игнорироваться, т.о. исключаясь из планирования. Пока планировщику не просигналят — что он уже готов "проснуться"
B>В общем то, не так все и страшно, значит...

То, что здесь писали про мьютекс и исполнение инструкций pause — это неправильно.
Для освобождения процессора нужно перейти в режим ядра и передать управление системному планировщику. Планировщик сохранит регистры потока в память, полностью прекратит его исполнение, и начнет исполнять на этом ядре какой-то другой поток другого процесса, или ядерный поток, если такие ждут квантов времени. В будущем планировщик может восстановить из памяти регистры усыпленного потока, в том числе переключить таблицу страниц, и вернуть его к исполнению.
Переход в режим ядра и тем более переключение контекста на другой поток — достаточно долгие операции.

B>Я думал что condition variable и mutex это некие фундаментальные, не сводимые друг к другу классы, оказывается, первое реализуется через второе?


Это разные операции. В теории одну можно свести к другой, но это не очень удобно.
И мьютекс, и условная переменная реализуются через системный вызов sys_futex().
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)
Отредактировано 17.05.2018 8:17 lpd . Предыдущая версия . Еще …
Отредактировано 17.05.2018 8:15 lpd . Предыдущая версия .
Re[10]: внутренняя реализация std::mutex?
От: barney  
Дата: 17.05.18 08:24
Оценка:
lpd>Переход в режим ядра и тем более переключение контекста на другой поток — достаточно долгие операции.

возникла идея безумная. вот смотрите, ведь все равно- потоки используют общую память. т.е им не нужен "защищенный режим" изоляции друг от друга
можно ли на уровне ОС сделать "user mode" потоки?
например юзермодовское прерывание таймера, которое не меняет контекст (user to kernel)?
т.о. потоки в рамках одного процесса могут быстро останавливаться — юзермод таймер прерыванием — быстро переключать стек регистров — и продолжать управление.
т.е если общий квант времени, отведенного на процесс не истек — то получим сверхшустрые потоки в рамках этого кванта

lpd>Это разные операции. В теории одну можно свести к другой, но это не очень удобно.

lpd>И мьютекс, и условная переменная реализуются через системный вызов sys_futex().

Понятно. Я так понимаю это смотреть имплементацию POSIX cond_variable в ядре линукс?
Re[12]: внутренняя реализация std::mutex?
От: barney  
Дата: 17.05.18 08:40
Оценка:
R> фибры в винде https://msdn.microsoft.com/en-us/library/windows/desktop/ms682661(v=vs.85).aspx

не совсем то — здесь поток сам планирует фибру, и она явно должна отдать управление назад

R>curoutines в обеих ОС


то же самое, проблема корутин — они явно должны делать yield()

имхо, тут нужно прерывание по таймеру, но не уводящее CPU в kernel mode
Re[12]: внутренняя реализация std::mutex?
От: barney  
Дата: 17.05.18 08:41
Оценка:
lpd>Раньше лет 15 назад в Linux потоки так и были реализованы без sys_futex(). Детали не знаю, но для переключения исполнения между потоками можно использовать user-level libc-функции setjmp()/longjmp().

любопытно! покопаю в эти setjmp()/longjmp()

но, подозреваю, там опять же без прерывания текущего потока исполнения по таймеру
т.е нечто вроде корутин
Re[13]: внутренняя реализация std::mutex?
От: lpd Черногория  
Дата: 17.05.18 08:43
Оценка:
Здравствуйте, barney, Вы писали:

B>но, подозреваю, там опять же без прерывания текущего потока исполнения по таймеру

B>т.е нечто вроде корутин

Нет, там было имнно переключение по таймеру. По-идее, вместо системного прерывания таймера можно просто использовать системные вызовы асинхронных таймеров.
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)
Re[13]: внутренняя реализация std::mutex?
От: reversecode google
Дата: 17.05.18 08:48
Оценка:
B>имхо, тут нужно прерывание по таймеру, но не уводящее CPU в kernel mode

я и не сказал что соцпрограммы работают по таймеру
но примеры которые я +10500 раз привел, все объясняют как это сделано у других и размазано по ядрам цпу
Re: внутренняя реализация std::mutex?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 17.05.18 09:01
Оценка:
Здравствуйте, barney, Вы писали:

B>но это приводит не просто к простою потока, а постоянной нагрузке


Есть два принципиально разных случая
1) ожидая освобождения, можно стать в очередь и ждать, пока кто-то не разбудит
2) будить некому, нет варианта, кроме как крутиться в цикле

Второй — это или межпроцессорные спинлоки за общий ресурс, или синхронизация между тредами/процессами/etc. при отсутствии толкового диспетчера.
Вариант "вместо кручения в цикле ждём на mwait" принципиально сути не меняет.

В первом, если не удалось захватить атомарным test-and-set или его аналогом (LL/SC на RISC), ставятся в очередь к диспетчеру, и тут коллеги уже расписали происходящее чуть более, чем во всех деталях.
The God is real, unless declared integer.
Re[3]: внутренняя реализация std::mutex?
От: smeeld  
Дата: 17.05.18 09:01
Оценка:
Здравствуйте, barney, Вы писали:


B>А POSIX функция pthread_mutex_lock уже, я так понимаю, использует некие функции ядра?

B>в LINUX это futex
B>асли это DARWIN/MACH то есть некие ядерные mach_ функции для блокировки/просыпания потоков, которые завязаны на мьютекс

POSIX API реализован как часть системной Cи либы. Функции этой либы, в свою очередь, опираются на функционал, предоставляемый ядром через сисколы. Это сисколы, в конечном итоге, дёргаются системными либами во в сех ОСях, и в DARWIN/MATCH. Сисколы базируются в предоставлении функционала на подсистемах ядра. Так, сискол вроде futex, уходит корнями в подсистему управления тредами в ядре. Чтоб узнать как реализовано оно всё в ядре-читайте исходники.
Re[2]: внутренняя реализация std::mutex?
От: barney  
Дата: 17.05.18 09:22
Оценка:
N>В первом, если не удалось захватить атомарным test-and-set или его аналогом (LL/SC на RISC), ставятся в очередь к диспетчеру, и тут коллеги уже расписали происходящее чуть более, чем во всех деталях.

Т.е без вызова диспатчера (и соотв. ухода в kernel mode сисколом) отключить поток от исполнения — нет способов?
Re[3]: внутренняя реализация std::mutex?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 17.05.18 09:42
Оценка:
Здравствуйте, barney, Вы писали:

N>>В первом, если не удалось захватить атомарным test-and-set или его аналогом (LL/SC на RISC), ставятся в очередь к диспетчеру, и тут коллеги уже расписали происходящее чуть более, чем во всех деталях.


B>Т.е без вызова диспатчера (и соотв. ухода в kernel mode сисколом) отключить поток от исполнения — нет способов?


Не-а
The God is real, unless declared integer.
Re: внутренняя реализация std::mutex?
От: AlexGin Беларусь  
Дата: 17.05.18 09:46
Оценка:
Здравствуйте, 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> // now in critical section

B> call do_work

B> mov flag, 0 // clean flag
B>leave:


Примерно так.
Вот подобное в C/C++ синтаксисе:
http://anki3d.org/spinlock
вот подобное при использовании boost:
https://www.boost.org/doc/libs/1_61_0/doc/html/atomic/usage_examples.html#boost_atomic.usage_examples.example_spinlock

B>но это приводит не просто к простою потока, а постоянной нагрузке

+100500
Да, но предполагается, что проектировщик/разработчик это понимает и не будет сильно злоупотеблять этим.
То есть — между входом (s.lock()) и выходом (s.unlock()) — делаем что-то весьма короткое с общим (совместным) ресурсом,
чтобы избежать взаимной блокировки.

spinlock s;

s.lock();
// здесь делаем что-то элементарное!
s.unlock();


B>при использовании std::mutex для критической секции такого не наблюдается.


По критическим секциям — вот интересная статья (от нашего коллеги Павла Блудова):
http://rsdn.org/article/baseserv/critsec.xml
Автор(ы): Павел Блудов
Дата: 14.03.2005
В статье рассматриваются аспекты работы с критическими секциями, их внутреннее устройство и способы отладки

В конце — важные выводы:
— Код, ограниченный критическими секциями, лучше всего свести к минимуму.
— Находясь в критической секции, не стоит вызывать методы "чужих" объектов.


B>как же работает мютекс?


В отличие от критической секции, мьютекс — объект системного ядра (в том же Windows API).
Предположу, что там "ожидание" это не простой цикл в условным/переходом на метку, я передача управления стстемному диспетчеру потоков.
В этом случае, задача STL (std::mutex) будет сведена просто к вызову соответствующих системных методов WinAPI или Posix.
Отредактировано 17.05.2018 9:56 AlexGin . Предыдущая версия . Еще …
Отредактировано 17.05.2018 9:51 AlexGin . Предыдущая версия .
Re: внутренняя реализация std::mutex?
От: barney  
Дата: 17.05.18 09:47
Оценка:
вот, сделал, учебную реализацию спин-блокировки через test-and-set для x64
На мак оси работает с ошибками,
в общем виде вывод смешан (как и должно быть, тк main thread не блочится)
но даже между обоими spinner почему то такая блокировка не работает:
"INSIDE THE LOCK" выводится не всегда неразрывно со spin_count
хотя здесь должна быть жесткая гарантия

#include <iostream>
#include <thread>

int mutex = 0;

inline void Lock(int * flag) {
    
    int spins_count = 0;
    
    __asm {
    outer:
        mov rbx,flag
        mov eax,[rbx]
        cmp eax,0
        jne outer
        
        xor ecx,ecx
        xor eax, eax
        mov rbx,flag
    loop:
        inc ecx
        lock bts [rbx], eax   // lock + bts atomic test-and-set
        jnc loop
        mov spins_count,ecx
    }
    // locked
    std::cout << "\nINSIDE_THE_LOCK:" << spins_count << "\n";
    __asm {
        mov rbx,flag
        xor eax,eax
        mov [rbx],eax
    }
}

void spinner(char * id) {
    
    while (true) {
        Lock(&mutex);
        // not locked
        std::cout << "·" << "SPINNER:" << id << "·";
    }
}

int main() {
    char * Q = "Q";
    char * W = "W";
    
    std::thread t1(spinner, Q);
    std::thread t2(spinner, W);
    
    while (true) {
        // not locked
        std::cout << "•";
    }
}


Вывод вот такой:
  вывод
•
INSIDE_THE_LOCK:••2•
•
INSIDE_THE_LOCK:·•2SPINNER:•
Q•··•SPINNER:
INSIDE_THE_LOCK:•W2•·
•
INSIDE_THE_LOCK:·•2SPINNER:•
Q•··•SPINNER:
INSIDE_THE_LOCK:•W2•·
•·
INSIDE_THE_LOCK:•SPINNER:2•
Q•··•SPINNER:
INSIDE_THE_LOCK:•W2•·
•·
INSIDE_THE_LOCK:•SPINNER:2•Q
•··•
INSIDE_THE_LOCK:SPINNER:•2W•
·•·
INSIDE_THE_LOCK:•SPINNER:2•Q
•··•
INSIDE_THE_LOCK:SPINNER:•2W•


Вот "INSIDE_THE_LOCK:SPINNER:•2W•"
здесь должно быть "INSIDE_THE_LOCK:2" или "INSIDE_THE_LOCK: ••2" но SPINNER должен был вывестись ПОСЛЕ
Re[2]: внутренняя реализация std::mutex?
От: barney  
Дата: 17.05.18 09:53
Оценка:
Спасибо за ссылочки, любопытно

AG>В конце — важные выводы:

AG>
AG>- Код, ограниченный критическими секциями, лучше всего свести к минимуму.
AG>- Находясь в критической секции, не стоит вызывать методы "чужих" объектов.


Воот...
Вообще бы, хотелось программировать в какой то иной парадигме,
без критических секций и мьютексов.
Мне нравится идея максимально заполнять ядра процессора работой,
с помощью work queue или thread pool
Увы, они реализованы внутри тоже с применением cond_variable и эти потоки "засыпают" через вызов функций ядра.
Т.к кусочки работы могут быть вполне небольшими — например обновить значение текстовой строки в UI по возвращению данных из сети и тд.
Впрочем, если бы можно было переключать потоки из user mode легковесным образом, (тут уже обсуждали выше user mode таймер)
была бы чистая шустрая и элегантная система воркеров!
Re[2]: внутренняя реализация std::mutex?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 17.05.18 09:55
Оценка:
Здравствуйте, barney, Вы писали:

B>вот, сделал, учебную реализацию спин-блокировки через test-and-set для x64


1. Что за компилятор?

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

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 или близкий, лучше так.
The God is real, unless declared integer.
Re[3]: внутренняя реализация std::mutex?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 17.05.18 09:59
Оценка:
Здравствуйте, barney, Вы писали:

B>Мне нравится идея максимально заполнять ядра процессора работой,

B>с помощью work queue или thread pool
B>Увы, они реализованы внутри тоже с применением cond_variable и эти потоки "засыпают" через вызов функций ядра.

Только когда нет работы. Когда она есть — им незачем спать.
Короткий момент в mutex_lock, когда другие воркеры разбираются с задачами и их отсутствием, не считаем.

B>Т.к кусочки работы могут быть вполне небольшими — например обновить значение текстовой строки в UI по возвращению данных из сети и тд.

B>Впрочем, если бы можно было переключать потоки из user mode легковесным образом, (тут уже обсуждали выше user mode таймер)
B>была бы чистая шустрая и элегантная система воркеров!

Это всё зависит от легковесности самих тредов.
The God is real, unless declared integer.
Re[2]: внутренняя реализация std::mutex?
От: lpd Черногория  
Дата: 17.05.18 10:02
Оценка:
Здравствуйте, 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 в конце почти всегда будет меньше, чем должно.
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)
Отредактировано 17.05.2018 10:03 lpd . Предыдущая версия .
Re[3]: внутренняя реализация std::mutex?
От: barney  
Дата: 17.05.18 10:06
Оценка:
Здравствуйте, 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 — тот захватил первым
Re[3]: внутренняя реализация std::mutex?
От: AlexGin Беларусь  
Дата: 17.05.18 10:16
Оценка:
Здравствуйте, barney, Вы писали:

B>Вообще бы, хотелось программировать в какой то иной парадигме,

B>без критических секций и мьютексов.

Всё зависит от задач.
Я обычно мьютексы не использую, так как это "тяжёлые" системные объекты.
Насчет критических секций — не вижу ничего предосудительного в их применении.
Часто я аккуратно пользуюсь ими. Здесь важно — НЕ вводить в облась, защищенную секцией много кода:
::EnterCriticalSection(m_pCS);
// Самые элементарные действия с общими ресурсами
::LeaveCriticalSection(m_pCS);


B>Мне нравится идея максимально заполнять ядра процессора работой,

B>с помощью work queue или thread pool
Как я понимаю, это примерно так:
https://stackoverflow.com/questions/3458397/boostthread-and-creating-a-pool-of-them

Заполнять ядра CPU работой — это палка о двух концах:
При работе таких приложений, компьютер очень медленно реагирует на действия пользователя.
Правильнее — где-то в циклах рабочих потоков вводить ::Sleep(100...500);
— чтобы отдать управление системе (исключить жуткие тормоза для end-user-а).
Re[4]: внутренняя реализация std::mutex?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 17.05.18 10:16
Оценка:
Здравствуйте, 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.
The God is real, unless declared integer.
Re[5]: внутренняя реализация std::mutex?
От: barney  
Дата: 17.05.18 10:24
Оценка:
N>Именно. И если CF=0, то надо не идти на цикл, а считать действие lock завершённым. Поэтому jc должно идти на цикл, а противоположность (прямой ход исполнения или jnc) — выходить из цикла.
N>Но дальше второе — чтобы не задрочить кэш, шины и всю систему постоянными попытками записи, если bts или аналог сорвались, снова идут на циклическое чтение, в ожидании, пока не появится 0.

Вот сделал без ассемблера, с std::atomic_flag по стопам приведенной тут в ссылках статьи

#include <iostream>
#include <thread>
#include <atomic>

class SpinLock
{
public:
    void lock()
    {
        while(lck.test_and_set(std::memory_order_acquire))
        {}
    }
    
    void unlock()
    {
        lck.clear(std::memory_order_release);
    }
    
private:
    std::atomic_flag lck = ATOMIC_FLAG_INIT;
};

//int mutex = 0;
SpinLock lock;

inline void Lock(int * flag) {
    
    int spins_count = 0;
    
//    __asm {
//    outer:
//        mov rbx,flag
//        mov eax,[rbx]
//        cmp eax,0
//        jne outer
//
//        xor ecx,ecx
//        xor eax, eax
//        mov rbx,flag
//    loop:
//        inc ecx
//        lock bts [rbx], eax   // lock + bts atomic test-and-set
//        jnc loop
//        mov spins_count,ecx
//    }
    
    lock.lock();
    
    // locked
    std::cout << "\nINSIDE_THE_LOCK:" << spins_count << "\n";
    
    lock.unlock();

//    __asm {
//        mov rbx,flag
//        xor eax,eax
//        mov [rbx],eax
//    }
}

void spinner(char * id) {
    while (true) {
        Lock(&mutex);
        // not locked
        std::cout << "·" << "SPINNER:" << id << "·";
    }
}

int main() {
    
    char * Q = "Q";
    char * W = "W";
    
    std::thread t1(spinner, Q);
    std::thread t2(spinner, W);
    
    while (true) {
        // not locked
        std::cout << "•";
    }
}



Все равно SPINNER разрывает вывод inside the lock
Re[5]: внутренняя реализация std::mutex?
От: barney  
Дата: 17.05.18 10:29
Оценка:
N>Именно. И если CF=0, то надо не идти на цикл, а считать действие lock завершённым. Поэтому jc должно идти на цикл, а противоположность (прямой ход исполнения или jnc) — выходить из цикла.

Точно!! JC ведь прыгает когда CF=1, те другой поток уже ухватил
Я пофиксил и заработало как надо

N>Но дальше второе — чтобы не задрочить кэш, шины и всю систему постоянными попытками записи, если bts или аналог сорвались, снова идут на циклическое чтение, в ожидании, пока не появится 0.


Вот, поэтому и нужен внешний цикл, который читает и ждет освобождения
и это можно делать неатомарно
Re[6]: внутренняя реализация std::mutex?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 17.05.18 10:36
Оценка:
Здравствуйте, barney, Вы писали:

B> void lock()

B> {
B> while(lck.test_and_set(std::memory_order_acquire))

UPD: стормозил, отменено
The God is real, unless declared integer.
Отредактировано 17.05.2018 12:10 netch80 . Предыдущая версия .
Re[7]: внутренняя реализация std::mutex?
От: barney  
Дата: 17.05.18 11:03
Оценка:
N> while(!lck.test_and_set(std::memory_order_acquire))

все таки без !

занятно что CLANG 6.0 вместо lock bts
делает вот что:

.LBB0_1:                                # =>This Inner Loop Header: Depth=1
        mov     al, 1
        xchg    byte ptr [rip + lock], al
        test    al, al
        jne     .LBB0_1


смотрю на https://godbolt.org/

он xchg считает атомарной инструкцией... странно это все...
Re[8]: внутренняя реализация std::mutex?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 17.05.18 12:22
Оценка:
Здравствуйте, 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 — не надо)! Кого бог хочет наказать, лишает разума...
The God is real, unless declared integer.
Re[8]: внутренняя реализация std::mutex?
От: reversecode google
Дата: 17.05.18 12:29
Оценка:
учитесь у яндекса

ну вы прям точно царь! только тот еще "всея руси"
а вы просто "провторяй"
Re[4]: внутренняя реализация std::mutex?
От: andrey.desman  
Дата: 17.05.18 13:59
Оценка:
Здравствуйте, AlexGin, Вы писали:

AG>Всё зависит от задач.

AG>Я обычно мьютексы не использую, так как это "тяжёлые" системные объекты.

С чего они тяжелые? Или речь о Windows?
Re[3]: внутренняя реализация std::mutex?
От: Videoman Россия https://hts.tv/
Дата: 17.05.18 15:25
Оценка:
Здравствуйте, andrey.desman, Вы писали:

AD>Серьезно? А как же очередь ожидающих и приоритизация? То есть, какой-то один поток может бесконечно долго висеть и не захватить мьютекс?


Я разве утверждал что ее там нет. Там очередь также реализована в user mope в виде списка с добавлением новых потоков в ее конец. Может быть вы все-таки глянете исходники? Так как std::mutex может "шариться" только внутри одного процесса, то я не вижу проблем всю логику реализовать вне ядра. Вы не согласны?
Re[4]: внутренняя реализация std::mutex?
От: lpd Черногория  
Дата: 17.05.18 15:29
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Я разве утверждал что ее там нет. Там очередь также реализована в user mope в виде списка с добавлением новых потоков в ее конец. Может быть вы все-таки глянете исходники? Так как std::mutex может "шариться" только внутри одного процесса, то я не вижу проблем всю логику реализовать вне ядра. Вы не согласны?


Для освобождения процессора необходимо перейти в режим ядра. Инструкция pause, про которую ты говорил, нужна для spinlock, но для mutex ее недостаточно.
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)
Re[4]: внутренняя реализация std::mutex?
От: Videoman Россия https://hts.tv/
Дата: 17.05.18 15:38
Оценка:
Здравствуйте, 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%. Если меньше, значит где-то холостые ожидания — значит код не оптимален, с точки зрения многопоточности.
Re[5]: внутренняя реализация std::mutex?
От: Videoman Россия https://hts.tv/
Дата: 17.05.18 15:41
Оценка:
Здравствуйте, lpd, Вы писали:

lpd>Для освобождения процессора необходимо перейти в режим ядра. Инструкция pause, про которую ты говорил, нужна для spinlock, но для mutex ее недостаточно.


Возможно я не внимательно смотрел. В любом случае, если spin-lock не успел — деваться некуда, придется уходить в ядро, но это гораздо эффективней чем просто пользоваться Mutex-ом.
Re[6]: внутренняя реализация std::mutex?
От: AlexGin Беларусь  
Дата: 17.05.18 15:49
Оценка:
Здравствуйте, 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.
У меня есть приложение, которое (выполняя определенную работу) — дико тормозит все мои пользовательские действия.
Что я могу сделать, как пользователь? Что могу сделать, если приложение сторонее и я НЕ имею его исходных кодов?
Отредактировано 17.05.2018 15:50 AlexGin . Предыдущая версия .
Re[4]: внутренняя реализация std::mutex?
От: andrey.desman  
Дата: 17.05.18 16:07
Оценка:
Здравствуйте, Videoman, Вы писали:

AD>>Серьезно? А как же очередь ожидающих и приоритизация? То есть, какой-то один поток может бесконечно долго висеть и не захватить мьютекс?


V>Я разве утверждал что ее там нет. Там очередь также реализована в user mope в виде списка с добавлением новых потоков в ее конец. Может быть вы все-таки глянете исходники? Так как std::mutex может "шариться" только внутри одного процесса, то я не вижу проблем всю логику реализовать вне ядра. Вы не согласны?


Не согласен, проблемы будут. Вне ядра получится слишком топорно.


Под Windows, например (VS2013), std::mutex реализован без использования вызовов WinAPI, почти так, как вы и описали, но spin блокировка крутится заданное число циклов (сейчас 4000) и уже по истечении этого времени квант потока отдается (он засыпает на pause). Фактически в С++ рантайм вынесены "кишки" стандартной critical section.


Без использования вызовов WinAPI. Нет.
Квант потока отдается (он засыпает на pause). Нет.

почти так, как вы и описали, но в описании был топорный спин-лок.

Нет, конечно, не утверждал, но учитывая вышенаписанное, ты скорее сам исходники смотрел не шибко внимательно.
Re[5]: внутренняя реализация std::mutex?
От: AlexGin Беларусь  
Дата: 17.05.18 16:09
Оценка:
Здравствуйте, 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>Вот ни разу не встречал такого (только если не долбиться с тяжелым запросом в ядро в цикле).

Хорошо, — вот допустим, что у меня такой вариант "ожидания":
#include <atomic>
 
class SpinLock
{
public:
    void lock()
    {
        while(lck.test_and_set(std::memory_order_acquire)) // здесь, возможно, придется подождать!
        {}
    }
 
    void unlock()
    {
        lck.clear(std::memory_order_release);
    }
 
private:
    std::atomic_flag lck = ATOMIC_FLAG_INIT;
};

Взято отсюда:
http://anki3d.org/spinlock

Теперь предположим, что в результате некоторой ситуации все ядра моего CPU ждут на while в методе lock.
Да, случай практически не вероятный, но всё-таки — полностью НЕ исключаемый.
Загрузка всех секций CPU будет под 100% (здесь я даже не спрашиваю насчет полезноти/бесполезности данной вычислительной работы).
Спрошу только одно — сможет ли пользователь хоть как-то взаимодействовать с ОС? Хоть бы для того, чтобы снять эту зависшую задачу?

V>Всегда свои программы профилировали чтобы загрузка СPU была 100%. Если меньше, значит где-то холостые ожидания — значит код не оптимален, с точки зрения многопоточности.

+100500
Да, с точки зрения многопоточности НЕ оптимален.
Но если я вынужден буду только "топить Reset", чтобы хоть как-то вмешаться в работу системы, то такой код также не является корректным
Re[5]: внутренняя реализация std::mutex?
От: AlexGin Беларусь  
Дата: 17.05.18 16:11
Оценка:
Здравствуйте, andrey.desman, Вы писали:

AD>Здравствуйте, AlexGin, Вы писали:


AG>>Всё зависит от задач.

AG>>Я обычно мьютексы не использую, так как это "тяжёлые" системные объекты.

AD>С чего они тяжелые? Или речь о Windows?

+100500
Да, именно про Windows!
Re[6]: внутренняя реализация std::mutex?
От: okman Беларусь https://searchinform.ru/
Дата: 17.05.18 18:09
Оценка:
Здравствуйте, 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.

Во-вторых, на однопроцессорной машине крутиться в спин-цикле бессмысленно, так как лок никто в
это время не освободит.

Ну и в-третьих, если между захватом и освобождением лока поток будет вытеснен, получится
очень некрасивая ситуация по отношению к другим потокам, которые ждут его освобождения.
Особенно если лок не гарантирует порядок захвата. Так легко и систему завесить, особенно если
такой спинлок применять где-нибудь в критических местах — драйверы, всякие системные
обработчики и т.д. Встречал на практике.
Re[5]: внутренняя реализация std::mutex?
От: Videoman Россия https://hts.tv/
Дата: 17.05.18 19:02
Оценка:
Здравствуйте, andrey.desman, Вы писали:

AD>Нет, конечно, не утверждал, но учитывая вышенаписанное, ты скорее сам исходники смотрел не шибко внимательно.


Да, просмотрел. В случае если, таки, необходимо засыпать, используется WaitForSingleObjectEx и поток уходит в ядро. Вся остальная реализация в user-mode. Т.е. все достаточно не плохо под Винду . Некоторые другие вещи в CRT меня ужасали, бывало.
Re[7]: внутренняя реализация std::mutex?
От: barney  
Дата: 17.05.18 19:11
Оценка:
Здравствуйте, 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>обработчики и т.д. Встречал на практике.

Любопытный вопрос. А какие практики используются вместо этого в ядре?
Re[6]: внутренняя реализация std::mutex?
От: Videoman Россия https://hts.tv/
Дата: 17.05.18 19:17
Оценка:
Здравствуйте, AlexGin, Вы писали:

AG>Хорошо, — вот допустим, что у меня такой вариант "ожидания":

AG>
AG>#include <atomic>
 
AG>class SpinLock
AG>{
AG>public:
AG>    void lock()
AG>    {
AG>        while(lck.test_and_set(std::memory_order_acquire)) // здесь, возможно, придется подождать!
AG>        {}
AG>    }
 
AG>    void unlock()
AG>    {
AG>        lck.clear(std::memory_order_release);
AG>    }
 
AG>private:
AG>    std::atomic_flag lck = ATOMIC_FLAG_INIT;
AG>};
AG>

AG>Взято отсюда:
AG>http://anki3d.org/spinlock

AG>Теперь предположим, что в результате некоторой ситуации все ядра моего CPU ждут на while в методе lock.

AG>Да, случай практически не вероятный, но всё-таки — полностью НЕ исключаемый.
AG>Загрузка всех секций CPU будет под 100% (здесь я даже не спрашиваю насчет полезноти/бесполезности данной вычислительной работы).
AG>Спрошу только одно — сможет ли пользователь хоть как-то взаимодействовать с ОС? Хоть бы для того, чтобы снять эту зависшую задачу?

Ну а зачем так делать? Вот вам принципы из практики на счет производительности, чтобы с многопоточностью все было ок:
1. Как не странно. не разделяйте данные. Если можно, всегда делайте локальные копии данных для каждого потока.
2. Старайтесь все данные через блокировки передавать пакетировано и как можно быстрее. В идеале, все должно сводится к обмену указателей на массив объектов.
3. Spin-Lock дает выигрыш только если количество итераций достаточно мало. В противном случае выгоднее уйти в ядро и не тратить циклы CPU.
Re[8]: внутренняя реализация std::mutex?
От: okman Беларусь https://searchinform.ru/
Дата: 17.05.18 19:21
Оценка:
Здравствуйте, barney, Вы писали:

B>А такое можно реализовать легковесно в user mode не отдавая контекст ядру/планировщику?


Наверное, ничего лучше инструкции pause пока не придумали.

B>Любопытный вопрос. А какие практики используются вместо этого в ядре?


Конкретно ядро Windows перед захватом спинлока задирает IRQL до DISPATCH_LEVEL, это предотвращает вытеснение
потока планировщиком (хотя это не исключает, например, приход прерывания, в результате чего код все равно
будет временно вытеснен). Ну и вместо обычных спинлоков рекомендуется применять спинлоки с очередями на
стеке (in-stack queued spinlock) — там каждый поток вместо глобальной переменной теребит переменную у
себя в стеке, ну и гарантируется порядок захвата спинлока потоками (т.е. убирается проблема "starvation").

Про linux не в курсе, я его видел только "на картинках"

В режиме пользователя запретить вытеснение потока нельзя. Думаю, в режиме пользователя по этой
причине никаких спинлоков быть не должно.
Отредактировано 17.05.2018 19:22 okman . Предыдущая версия .
Re[7]: внутренняя реализация std::mutex?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 17.05.18 20:32
Оценка:
Здравствуйте, 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... боюсь, что тут ровно как в знаменитом лозунге про спасение утопающих...
The God is real, unless declared integer.
Re[6]: внутренняя реализация std::mutex?
От: Vain Россия google.ru
Дата: 17.05.18 23:30
Оценка:
Здравствуйте, reversecode, Вы писали:

R>коллективный разум это когда решается какая то проблема доселе не известная или не имеющая решения

R>а как устроены ОС описано в гугле на каждом шагу, плюс куча книг и исходников
R>это называется по другому, — научите меня бесплатно
А сам то ты здесь наверно ошиваешься не для того чтобы учить/учиться, а флеймить ради?
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Re[7]: внутренняя реализация std::mutex?
От: AlexGin Беларусь  
Дата: 18.05.18 09:02
Оценка:
Здравствуйте, 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...
Отредактировано 18.05.2018 9:33 AlexGin . Предыдущая версия . Еще …
Отредактировано 18.05.2018 9:08 AlexGin . Предыдущая версия .
Re[7]: внутренняя реализация std::mutex?
От: AlexGin Беларусь  
Дата: 18.05.18 09:22
Оценка:
Здравствуйте, Videoman, Вы писали:

AG>>Теперь предположим, что в результате некоторой ситуации все ядра моего CPU ждут на while в методе lock.

AG>>Да, случай практически не вероятный, но всё-таки — полностью НЕ исключаемый.
AG>>Загрузка всех секций CPU будет под 100% (здесь я даже не спрашиваю насчет полезноти/бесполезности данной вычислительной работы).
AG>>Спрошу только одно — сможет ли пользователь хоть как-то взаимодействовать с ОС? Хоть бы для того, чтобы снять эту зависшую задачу?

V>Ну а зачем так делать? Вот вам принципы из практики на счет производительности, чтобы с многопоточностью все было ок:

V>1. Как не странно. не разделяйте данные. Если можно, всегда делайте локальные копии данных для каждого потока.

Есть немалая вероятность, что когда-либо эти данные окажутся рассинхронизированы (будут иметь различные значения, вместо одного).

V>2. Старайтесь все данные через блокировки передавать пакетировано и как можно быстрее. В идеале, все должно сводится к обмену указателей на массив объектов.

+100500
Это бесспорно так.

V>3. Spin-Lock дает выигрыш только если количество итераций достаточно мало. В противном случае выгоднее уйти в ядро и не тратить циклы CPU.

+100500
Здесь также согласен.
Re[6]: внутренняя реализация std::mutex?
От: AlexGin Беларусь  
Дата: 18.05.18 09:40
Оценка:
Здравствуйте, lpd, Вы писали:

lpd>Здравствуйте, AlexGin, Вы писали:


AG>Да, но есть риск получить систему полностью загруженной, когда она перестанет отзываться на действия пользователя.

AG>Вполне возможно, что в цикле рабочего потока ::Sleep(100) — не вызовет катастрофических задержек.
AG>Однако, вызвав диспетчер потоков позволит пользователю как-то общаться с машиной, когда запущена твоя вычислительная задача.
AG>В противном случае — компьютер становиться страшно тормознутым и практически не управляемым

lpd>Лучше уменьшить приоритет вычислительной задаче через системный менеджер процессов, например, или из кода.


Так получится, что уменьшение приоритета даст примерно тот же эффект (если не хуже),
в плане снижения производительности, что тот же самый ::Sleep
Re: внутренняя реализация std::mutex?
От: barney  
Дата: 18.05.18 14:59
Оценка:
Нашел любопытный разбор легковесных мьютексов под win32

http://preshing.com/20120226/roll-your-own-lightweight-mutex/
Re[8]: внутренняя реализация std::mutex?
От: okman Беларусь https://searchinform.ru/
Дата: 18.05.18 15:07
Оценка:
Здравствуйте, AlexGin, Вы писали:

AG>Вероятность того, что поток будет вытеснен, если внутри этого лока — только один вызов гетера/сетера — фактически нулевая.


Смысл в том, что она все равно не нулевая.
У нас в софте были такие зависания, оказалось там была схожая реализация лока с циклом на CMPXCHG.
Код под локом — всего несколько процессорных инструкций, но все равно иногда случалось, что
он вытеснялся и система висла (потому что за лок конкурировали другие важные потоки системы).
Когда переделали на стандартные виндовые spinlock/pushlock — все заработало.

AG>Каких-либо механизмов, способных разрулить проблему IMHO не существует.

AG>Кроме, разве что сделать ожидание в локе — с прокачкой сообщений ОС, и с вызовом Sleep...

Ну в ядре, как я уже писал, можно на время запретить переключение контекстов, ядерные
спинлоки так и работают (повышая IRQL до соответствующего уровня, где шедулер не работает).
Re[2]: внутренняя реализация std::mutex?
От: Videoman Россия https://hts.tv/
Дата: 19.05.18 09:00
Оценка:
Здравствуйте, barney, Вы писали:

B>Нашел любопытный разбор легковесных мьютексов под win32


А что в нем легковесного? В случае неудачи сразу же блокировать конкурента. Обыкновенная критическая секция намного эффективнее будет.
Re[3]: внутренняя реализация std::mutex?
От: AlexGin Беларусь  
Дата: 19.05.18 10:23
Оценка:
Здравствуйте, okman, Вы писали:

O>На Windows Vista и выше есть вот это:


O>Slim Reader/Writer (SRW) Locks

O>https://msdn.microsoft.com/en-us/library/windows/desktop/aa904937(v=vs.85).aspx

O>Это легковесный аналог mutex/крит.секции, но поддерживающий семантику 'читатели-писатель'.


Интересная штуковина!
Вот нагуглил примерчик по применению:
https://www.codeproject.com/Articles/32685/Testing-reader-writer-locks
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.