Re: C++11: Синхронизация - Условные переменные и ложные проб
От: kotalex  
Дата: 13.03.19 08:42
Оценка: 33 (6) -2
Здравствуйте, rsdn_179b, Вы писали:

Интересует собственно сабж — откуда берутся эти самые "ложные пробуждения" ?

Для POSIX: дело в устройстве самого механизма. Pthread основан на futex. В основе futex идёт (первым аргументом) адрес некоторой переменной (назовём её 'A' в некотором "подопытном" процессе). Далее внутри реализации происходит преобразование адреса переменной из "пользовательского пространства" в физический адрес (это нужно для shared операций между процессами). Далее, от полученного адреса вычисляется 32 битный хэш (так называемый jhash2). Затем вычисляется индекс: от полученного значения берётся маска, что-то типа ( hash & ((1<<20) — 1 ) ). Значение этой маски сильно зависит от системы (настроек, количества процессоров), но обычно она не превышает 1 МБ. Полученный индекс используется для индексации в глобальной таблице, описывающей все futex-объекты. В итоге, если есть в системе какой-либо другой процесс, у которого есть своя переменная 'B' и индекс, подсчитанный от этой переменной совпадёт с индексом переменной 'A' и "выставится событие" по переменой 'B' — то выставится это-же событие и для переменной 'A', т.е. для неё ('A') произойдёт "ложное пробуждение". Вероятность данного явления небольшая, но всё-же есть.
Отредактировано 13.03.2019 9:44 kotalex . Предыдущая версия . Еще …
Отредактировано 13.03.2019 9:38 kotalex . Предыдущая версия .
Отредактировано 13.03.2019 9:36 kotalex . Предыдущая версия .
Отредактировано 13.03.2019 9:34 kotalex . Предыдущая версия .
Re[2]: C++11: Синхронизация - Условные переменные и ложные проб
От: watchmaker  
Дата: 17.03.19 23:38
Оценка: 18 (3) +2
Здравствуйте, kotalex, Вы писали:

K> В основе futex идёт (первым аргументом) адрес некоторой переменной (назовём её 'A' в некотором "подопытном" процессе). Далее внутри реализации происходит преобразование адреса переменной из "пользовательского пространства" в физический адрес (это нужно для shared операций между процессами). Далее, от полученного адреса вычисляется 32 битный хэш (так называемый jhash2). Затем вычисляется индекс: от полученного значения берётся маска, что-то типа ( hash & ((1<<20) — 1 ) ). Значение этой маски сильно зависит от системы (настроек, количества процессоров), но обычно она не превышает 1 МБ. Полученный индекс используется для индексации в глобальной таблице, описывающей все futex-объекты.


Вот до этого места правильно описывается одна из возможных реализаций, но дальше идёт просто фееричная чушь:

K>В итоге, если есть в системе какой-либо другой процесс, у которого есть своя переменная 'B' и индекс, подсчитанный от этой переменной совпадёт с индексом переменной 'A' и "выставится событие" по переменой 'B' — то выставится это-же событие и для переменной 'A', т.е. для неё ('A') произойдёт "ложное пробуждение". Вероятность данного явления небольшая, но всё-же есть.


То что несколько фьютексов могут отображаться в один бакет таблицы никак не делает их зависимыми. Они (как и в любой нормальной хеш-таблице без открытой адресации) просто будут оба лежать в одном бакете. Это хеширование с цепочками называется.
Вот в ядре linux есть функция match_futex, которая используется во всех операциях обхода бакета как раз для того, чтобы отфильтровать фьютексы попавшие в него из-за коллизий.

То есть тут даже не важно что используется: хеш-таблица, какое-нибудь дерево поиска, или вообще единственная глобальная очередь на всё ядро. Различия будут в производительности (очевидно, глобальная очередь будет медленновато работать из-за параллельных локов), но не в семантике операций.

---

Да и вообще, даже если допустить, что ядро не имеет возможностей отличить где чей фьютекс находится, а сравнивает их только по хешу, то тогда невозможно было бы реализовать функции вроде pthread_cond_signal, которые будят ровно один тред, а не все. Ведь вызов pthread_cond_signal мог бы разбудить своего доппельгангера (для которого бы это пробуждение выглядело как spurious wakeup), и, соответственно, не разбудить поток ожидающий на целевом фьютексе. А это, внезапно, куда большая проблема: в системе образовался поток, которому не дошел сигнал пробуждения и который завис навечно (если конечно, ещё раз не произойдёт чудо и не придёт сигнал на пробуждение от какого-то третьего неудачника с совпавшим хешом ). Вы регулярно наблюдаете такие зависшие потоки? А знаете почему нет?

Призываю не верить объяснению kotalex — оно прикольное, но совершенно неверное.
Re[2]: C++11: Синхронизация - Условные переменные и ложные пробужде
От: jazzer Россия Skype: enerjazzer
Дата: 06.04.15 06:38
Оценка: 14 (3)
Здравствуйте, rsdn_179b, Вы писали:

_>>Интересует собственно сабж — откуда берутся эти самые "ложные пробуждения" ?


Кстати, вот хороший кусочек из обсуждения на SO:

The pthread_cond_wait() function in Linux is implemented using the futex system call. Each blocking system call on Linux returns abruptly with EINTR when the process receives a signal. ... pthread_cond_wait() can't restart the waiting because it may miss a real wakeup in the little time it was outside the futex system call. This race condition can only be avoided by the caller checking for an invariant. A POSIX signal will therefore generate a spurious wakeup.

Summary: If a Linux process is signaled its waiting threads will each enjoy a nice, hot spurious wakeup.

коммент

This EINTR unblocking is true of all blocking system calls in Unix derived systems. This made the kernel lots simpler, but the application programmers bought the burden.

вопрос

I thought pthread_cond_wait() and friends could not return EINTR, but return zero if spuriously woken up? From: pubs.opengroup.org/onlinepubs/7908799/xsh/… "These functions will not return an error code of [EINTR]."

и ответ

That's right. The underlying futex() call returns EINTR, but that return value isn't bubbled up to the next level. The pthread caller must therefore check for an invariant. What they're saying is that when pthread_cond_wait() returns you must check your loop condition (invariant) again, because the wait might have been spuriously woken up. Receiving a signal during a system call is one possible cause, but it's not the only one.

http://stackoverflow.com/questions/1050592/do-spurious-wakeups-actually-happen

Имхо, вполне исчерпывающе: особенности реализации плюс некоторая кривизна интерфейса (что EINTR не возвращается, даже если он является причиной вылета из блокирующего ожидания).
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re: C++11: Синхронизация - Условные переменные и ложные пробужде
От: jazzer Россия Skype: enerjazzer
Дата: 02.04.15 02:16
Оценка: 8 (2) +1
Здравствуйте, rsdn_179b, Вы писали:

_>Интересует собственно сабж — откуда берутся эти самые "ложные пробуждения" ?


_>П.С.: Ссылки на соответствующие учёные книги приветствуются


Вот тут есть ссылка на ученую книгу и очень занимательный кусочек переписки с ее автором, рекомендую прочитать
http://en.wikipedia.org/w/index.php?title=Spurious_wakeup&amp;oldid=340776664
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[2]: C++11: Синхронизация - Условные переменные и ложные пробужде
От: uzhas Ниоткуда  
Дата: 03.04.15 12:14
Оценка: +3
Здравствуйте, Кодт, Вы писали:

К>Держать по отдельной CV на каждый бит — расточительно; цена экономии — ложные пробуждения, если поток-источник выставил не тот бит, который нам интересен.


ТС спрашивает о spurious wakeups, а ты подменяешь понятия и ушел совсем не в ту степь
Re: C++11: Синхронизация - Условные переменные и ложные пробужде
От: Кодт Россия  
Дата: 02.04.15 20:48
Оценка: -2
Здравствуйте, rsdn_179b, Вы писали:

_>Интересует собственно сабж — откуда берутся эти самые "ложные пробуждения" ?


1) Если есть несколько точек ожидания одной и той же CV — будь то один поток, интересующийся разными вещами, или несколько потоков, ворующих посылки друг у друга из-под носа
2) Если условие может меняться туда-сюда быстрее, чем заинтересованный в нём поток

Пример:
mutex m;
conditional_variable cv;
DWORD flags;

void update(DWORD set, DWORD reset)
{
  scoped_lock l(m);
  DWORD newflags = (flags & ~reset) | set;
  // порядок извещения и изменения не важен, мы же ещё не отпустили мьютекс
  if(flags != newflags) cv.notify_one();
  flags = newflags;
}

void expect(DWORD flag, bool isset)
{
  unique_lock<mutex> l(m);
  while(bool(flags & flag) != isset) cv.wait(l);
}

Держать по отдельной CV на каждый бит — расточительно; цена экономии — ложные пробуждения, если поток-источник выставил не тот бит, который нам интересен.

Ещё пример
volatile unsigned ready_flag;

void update(bool flag)
{
  ::InterlockedExchange(&ready_flag, flag); // запись атомарная, лочить незачем
  cv.notify_one(); // но известить потребителя нужно
  // если поменяем на notify_all для второго сценария, - ничего не изменится
}

void take_ready() // ждёт ready_flag и опускает его
{
  unique_lock<mutex> l(m); // чисто для протокола - так требует CV
  while(!::InterlockedCompareExchange(&ready_flag,false,true)) cv.wait(l);
}

// первый сценарий
void update_nothing() { update(true); update(false); } // дёрнули, даже дважды, но пока поток-приёмник проснётся...

// второй сценарий
void update_twice() { update(true); update(true); } // дёрнули дважды
void thread_one()   { take_ready(); } // первый проснулся и погасил
void thread_two()   { take_ready(); } // второй проснулся, а уже погашено


_>Зачем крутиться в цикле и проверять какие-то условия


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

CV — это способ извещения о событии, о неком моменте, в котором, возможно, выполнилось нужное условие.
Говоря в терминах WinAPI, это PulseEvent.
Связывать CV с состоянием и экономить на проверках и булевских флажках — можно в некоторых узких рамках. (В отличие от Event, которое и событие, и булев флажок, или от семафора, который и событие, и счётчик).
Мне кажется, именно эта особенность WinAPI приучила программистов к иным сценариям синхронизации, чем во всём остальном мире. И то, проблема ложных пробуждений там тоже существует.
Перекуём баги на фичи!
Re[2]: C++11: Синхронизация - Условные переменные и ложные пробужде
От: Vain Россия google.ru
Дата: 12.04.15 11:52
Оценка: +2
Здравствуйте, Кодт, Вы писали:

_>>Интересует собственно сабж — откуда берутся эти самые "ложные пробуждения" ?

К>Пример:
первый раз вижу чтобы кодт минусов нахватал
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Re[7]: C++11: Синхронизация - Условные переменные и ложные п
От: kotalex  
Дата: 28.03.19 09:08
Оценка: -1 :)
Здравствуйте, netch80, Вы писали:

А вы не читаете код и не разбираетесь в терминологии.
"bucket" в хэш-таблицах — это место для нахождения всех элементов, у которых результат применения хэш-функции показывает индекс этого бакета (для closed addressing, как в данном случае).
Не одного, а всех. В hash_futex() последней строкой: return &futex_queues[hash & (futex_hashsize — 1)];

У Вас талант отвечать на вопрос, который не задавался
Отредактировано 28.03.2019 9:49 kotalex . Предыдущая версия . Еще …
Отредактировано 28.03.2019 9:08 kotalex . Предыдущая версия .
Re: C++11: Синхронизация - Условные переменные и ложные проб
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 21.03.19 21:17
Оценка: 4 (1)
Здравствуйте, rsdn_179b, Вы писали:

_>Зачем крутиться в цикле и проверять какие-то условия


Futex, как некоторые коллеги пишут тут, ни при чём. Правило про возможность ложных пробуждений возникло задолго до Linux и futex. Оно было в BSD в select(). Оно было в изначальных версиях многониточности на Unix (которые появились на SysV, как Solaris), и перенесено 1:1 в Java. Оно было в Bell Unix в ядерном sleep/wakeup (там вместо мьютексов были уровни приоритета ядра, но суть та же). Более того, этот механизм в таком виде существовал ещё до Unix, и там тоже было это правило.

Настоящая и исконная причина: в multi-producer-multi-customer построении невозможно гарантировать, что когда потребителю A "свистнули", что появился ресурс, не придёт потребитель B, который захватит мьютекс раньше и потребит ресурс. Когда же B отпустит мьютекс и A получит такую возможность, ему уже может не достаться ресурса, он увидит другое состояние, чем предполагалось на момент отдачи нотификации.

Частным "ракурсом" этой проблемы является ABA problem, но ситуация к ней сводится только в простых случаях (перед спячкой увидели какое-то состояние, вышли из спячки — а там вернулись к нему же; в сложных случаях состояние будет другое, хотя тоже неинтересное).

Если кто-то спросит, почему не делать такую синхронизацию, при которой нет такой проблемы... можно сделать. Например, при notifyOne() указывать, кого именно notify. Но тогда другая проблема — а что, если этот решил вообще выйти из игры? Ему надо переслать на кого-то другого? Увы, за >50 лет существования темы такой синхронизации — этот подход самый простой и надёжный.

А уже последствием описанной проблемы является возможность разрешения ложных пробуждений за счёт коллизий реализации (про futex — скорее сказка, а вот select collision в старых BSD была известным явлением).
The God is real, unless declared integer.
Отредактировано 29.03.2019 6:57 netch80 . Предыдущая версия . Еще …
Отредактировано 21.03.2019 21:48 netch80 . Предыдущая версия .
Re[6]: C++11: Синхронизация - Условные переменные и ложные проб
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 21.03.19 21:45
Оценка: 4 (1)
Здравствуйте, Videoman, Вы писали:

A>>Чем тебя не устраивает объяснение, что cond_wait вылетает при получении процессом сигнала ?


V>Не совсем устраивает, т.к. не хватает информации в общем. Я так понял:

V>у Linux все ожидания, всех объектов в ядре так устроены — происходит пробуждение по сигналу со специальным кодом. Т.к. в случаях не связанных с CV у нас состояние объекта присутствует в ядре, мы можем проверить код возврата, перепроверить само состояние и вернуться обратно в ожидание, если оно было ложным. В случае в CV у нас состояние в user mod-е и мы вынуждены вернуться и проверять его самостоятельно.

Эта логика не подтверждается. У сигнала в схеме Unix есть настройка перезапуска (по каждому сигналу отдельно, см. SA_RESTART в sigaction(). И есть список вызовов, которые не перезапускаются даже при SA_RESTART — тут — и в него входят все вызовы ожидания события (select, poll, *sleep и так далее).
Тем не менее, согласно последней ссылке, futex wait перезапускается. То есть сигналы (по крайней мере с 2.6.22, то есть, по состоянию на последние ~10 лет) ни при чём.

V> Из всего вышесказанного делаем вывод — futex оказался слишком низкоуровневым объектом что бы его выносить в стандартную библиотеку C++. Ведь никто, слава богу, не догадался сделать spurious_read, spurious_join или spurious_lock.


Если имеется в виду, что read может получить отказ по сигналу — то догадался и делает. Это не относится только к uninterruptible waits, которые в основном относятся к FS, ну и исторически к non-direct raw disk I/O.

V>3. Почему, например, не сделали флаг для futex-а — NO_SPURIOUS_WAKEUP для использования в высокоуровневом API ?


Я написал в комментарии к исходному сообщению, почему это не имеет смысла.

V>4. Что с вариантами wait_until и wait_for, ведь если wait_until еще как-то можно в цикле крутить, то wait_for становится весьма нетривиальным ?


Сейчас wait_for тривиально везде (или в Windows не так?) можно переделать в wait_until по монотонному времени.
The God is real, unless declared integer.
Re: C++11: Синхронизация - Условные переменные и ложные пробужде
От: B0FEE664  
Дата: 02.04.15 15:53
Оценка: +1
Здравствуйте, rsdn_179b, Вы писали:

_>Интересует собственно сабж — откуда берутся эти самые "ложные пробуждения" ?

Ну, а что вы хотите от переменной с таким названием? Условно говоря, она работает. Иногда, правда, wait выходит из себя залочив мьютекс без всяких видимых на то причин, но это не страшно: мьютекс-то залочен, а значит данные не испортятся.

_>Поиск по форуму к сожалению не дал ответа на этот вопрос.

Я так понимаю, что проблема эта тянется с самого низу, с аппаратного уровня.

_>Но к сожалению не объясняет самой сути этого явления.

Суть явления: функция wait(..) может разблокироваться, даже если notify_one() никто не вызывал. Так написано в стандарте и поэтому нам с этим приходится жить.

_>Зачем крутиться в цикле и проверять какие-то условия

Затем, что wait(..) может самопроизвольно разблокироваться и функция её вызвавшая продолжит работу в любой, непредсказуемый момент. Никто notify_one() не вызывал, а она раз — и разблокировалась. Поэтому и вводят флажок, чтобы отличить самопроизвольное разблокирование от запланированного.

_>Пример кода взят с "CodeProject":

Во взятом примере выкинут важный lock вот здесь:
_>
_>void workerfunc(int id, std::mt19937& generator)
_>{
_>    ...
        std::unique_lock<std::mutex> locker(g_lockqueue);
_>    g_notified = true;
_>    g_queuecheck.notify_one();
_>    ...
_>}
_>

Изменение g_notified должно быть защищено.

_>Надеюсь на содержательные ответы. Всем ответившим заранее спасибо.

Содержательно jazzer уже ответил. От себя добавлю, что условные переменные — это не события. Это даже не переменные, так как никаких данных они не хранят. Это такой неудобный способ синхронизации, который нужен для сверхмеры оптимизированных алгоритмов. Лично я его напрямую не использую. Я из условной переменной делаю событие для нотификации, а данные защищаю отдельным мьютексом. Так код становится существенно проще, хотя и не для всех задач можно построить решение на событиях. Те же, кто экономит на спичках мьютексах, использует condition variables.
И каждый день — без права на ошибку...
Re: C++11: Синхронизация - Условные переменные и ложные пробужде
От: Bork СССР  
Дата: 05.04.15 02:29
Оценка: +1
Здравствуйте, rsdn_179b, Вы писали:

_>Здравствуйте гуру, знатоки и просто ценители C++ !


_>Интересует собственно сабж — откуда берутся эти самые "ложные пробуждения" ?


В книге "Стандартная библиотека С++" Николаи М. Джосаттис, 2 издание, 2014г., с. 1028

"... проверка условной переменной может сработать, даже если условная переменная
не осуществляла уведомление. Процитируем Энтони Уильямса (Anthony Williams)
[Williams:CondVar]:"Ложные срабатывания невозможно предсказать: с точки зрения пользователя
они являются совершенно случайными. Однако они часто происходят, когда библиотека потоков
не может гарантировать, что ожидающий поток не пропустит уведомление. Поскольку пропущенное
уведомление делает условную переменную бесполезной, библиотека потоков активизирует потоки,
чтобы не рисковать."

Все выше — цитата из переведенной книжки, кому инересно можете поискать в оригинале

Да, Джосаттис ссылается на:
Anthony Williams. Multithreading and Concurrency: Condition Variable Spurious Wakes
http://www.justsoftwaresolutions.co.uk/threading/?page=2?

правда прямого аналога цитате я там не нашел
Re[2]: C++11: Синхронизация - Условные переменные и ложные п
От: andyp  
Дата: 08.04.15 20:38
Оценка: +1
Здравствуйте, se_sss, Вы писали:

_>В связи с этой страничкой возник вопрос. А что если у нас несколько потоков? В каком из них ошибка произошла?

_>errno ведь глобальная переменная?

Использлвание errno должно быть потокобезопасно по требованиям POSIX. Например в Линуксе эта переменная thread-local
http://linux.die.net/man/3/errno

PS Здесь jazzer поясняет, почему бессмысленно проверять EINTR в пользовательском коде:
http://rsdn.ru/forum/cpp/6005100.1
Автор: jazzer
Дата: 06.04.15
Отредактировано 08.04.2015 20:50 andyp . Предыдущая версия . Еще …
Отредактировано 08.04.2015 20:49 andyp . Предыдущая версия .
Re[5]: C++11: Синхронизация - Условные переменные и ложные п
От: kotalex  
Дата: 28.03.19 07:47
Оценка: -1
Здравствуйте, watchmaker, Вы писали:

// проверка на коллизии
if (match_futex (&this->key, &key))

Вы не внимательно читаете заданный вопрос — это не проверка на коллизии, а вполне конкретный код для нахождения заданного бакета, чтобы, например, пробудить его !
Отредактировано 28.03.2019 7:48 kotalex . Предыдущая версия . Еще …
Отредактировано 28.03.2019 7:48 kotalex . Предыдущая версия .
Re[6]: C++11: Синхронизация - Условные переменные и ложные п
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 28.03.19 08:19
Оценка: -1
Здравствуйте, kotalex, Вы писали:

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

K>

K>// проверка на коллизии
K>if (match_futex (&this->key, &key))

K>Вы не внимательно читаете заданный вопрос — это не проверка на коллизии, а вполне конкретный код для нахождения заданного бакета, чтобы, например, пробудить его !

А вы не читаете код и не разбираетесь в терминологии.
"bucket" в хэш-таблицах — это место для нахождения всех элементов, у которых результат применения хэш-функции показывает индекс этого бакета (для closed addressing, как в данном случае).
Не одного, а всех. В hash_futex() последней строкой:
    return &futex_queues[hash & (futex_hashsize - 1)];

В каждом бакете находится голова списка структур ожидания.

А уже найдя конкретный бакет, начинается итерирование того, что в него попало — и вот тут уже через match_futex() проверяется конкретный элемент, описывающий ожидающий futex.
The God is real, unless declared integer.
Re[8]: C++11: Синхронизация - Условные переменные и ложные п
От: B0FEE664  
Дата: 28.03.19 12:43
Оценка: :)
Здравствуйте, netch80, Вы писали:

N>В первом абзаце исходного сообщения я сказал про текущий режим, а во втором слегка затронул возможные альтернативы (и снова, полагал это отличие очевидным). Но больше я эти альтернативы обсуждать не хочу, потому что обсуждение мгновенно потеряло целостность.


Мне с самого начала изучения condition_variable ничего очевидным с использованием этого конструкта не кажется. Начиная с совершенно непонятного и вводящего в заблуждение названия и кончая ничем не обоснованным непредсказуемым поведением.

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

Поэтому я не вижу никакого прямого способа использования этого примитива, кроме как превратив CV в что-то более логичное, например в объект Event (Событие).

Я понимаю трудности изложенные здесь:
N>Настоящая и исконная причина: в multi-producer-multi-customer построении невозможно гарантировать, что когда потребителю A "свистнули", что появился ресурс, не придёт потребитель B, который захватит мьютекс раньше и потребит ресурс. Когда же B отпустит мьютекс и A получит такую возможность, ему уже может не достаться ресурса, он увидит другое состояние, чем предполагалось на момент отдачи нотификации.

но я так и не понял, как эти трудности ведут к ложным пробуждениям CV.
И каждый день — без права на ошибку...
Re[9]: C++11: Синхронизация - Условные переменные и ложные п
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 28.03.19 14:44
Оценка: +1
Здравствуйте, B0FEE664, Вы писали:

BFE>Мне с самого начала изучения condition_variable ничего очевидным с использованием этого конструкта не кажется. Начиная с совершенно непонятного и вводящего в заблуждение названия и кончая ничем не обоснованным непредсказуемым поведением.


Понятно.
Ну а я считаю, что название — это единственное, что в ней нелогично. Исторически даже понятно, но всё равно нелогично, потому что чтобы объяснить название, надо подымать историю, и в достаточно глубинных деталях. Поэтому лучше говорить "тут так принято, просто запомните".

Если же обсуждать альтернативные названия, я бы начал с обсуждения чего-то вроде ghost pipe

BFE>Поэтому я не вижу никакого прямого способа использования этого примитива, кроме как превратив CV в что-то более логичное, например в объект Event (Событие).


Именно что напрямую и используется. Сделать Event на её основе, конечно, можно, но не подходит в заметной части случаев, и излишне в большинстве остальных.

BFE>Я понимаю трудности изложенные здесь:

N>>Настоящая и исконная причина: в multi-producer-multi-customer построении невозможно гарантировать, что когда потребителю A "свистнули", что появился ресурс, не придёт потребитель B, который захватит мьютекс раньше и потребит ресурс. Когда же B отпустит мьютекс и A получит такую возможность, ему уже может не достаться ресурса, он увидит другое состояние, чем предполагалось на момент отдачи нотификации.

BFE>но я так и не понял, как эти трудности ведут к ложным пробуждениям CV.


notifyOne() дёрнул согласно списку ожидающих задачу TA (была она при этом первой в очереди, последней или случайно выбранной — не важно). Когда шедулер дойдёт до TA, первым делом сработает залочивание мьютекса.
Но если почему-то оказалось, что задача TB раньше захватила этот мьютекс, то она может изменить состояние так, что активация задачи TA окажется бессмысленной.

Вот пример. Типовая реализация пула нитей для исполнения очереди заданий выглядит так: основной цикл псевдокода исполнителя:

  mx.lock();
  while (!flag_shutdown) {
    if (!workqueue.empty()) {
      Job job = workqueue.pop();
      mx.unlock();
      job();
      mx.lock();
    } else {
      cv.wait_for(mx, MAX_SLEEP);
    }
  }
  mx.unlock();


(Пропускаю тему обработки исключений и т.п.)

Пусть задача TB закончила выполнение очередной job и стала в очередь на мьютекс. Задача TC поставила новый job в пустую очередь и отпустила мьютекс. TC делает notify_one(), шедулер дёргает проснуться TA (TB сейчас не в ожидании, на неё не поставят). TA становится в очередь на мьютекс. TC отпускает мьютекс (до notify_one() или после — почти без разницы), TB захватывает мьютекс, начинает выполняться, забирает задание, очередь становится пустой. TA приходит за заданием, а его нет — TB забрала раньше. TA идёт спать на новый круг.

Оптимизировав логику шедулера, можно сократить частоту таких левых пробуждений, но свести до нуля — не получается. А ещё есть случаи старта новых задач в пул, и с ними надо тоже синхронизироваться.
The God is real, unless declared integer.
Отредактировано 29.03.2019 6:50 netch80 . Предыдущая версия .
C++11: Синхронизация - Условные переменные и ложные пробужде
От: rsdn_179b  
Дата: 01.04.15 23:29
Оценка:
Здравствуйте гуру, знатоки и просто ценители C++ !

Интересует собственно сабж — откуда берутся эти самые "ложные пробуждения" ?

Поиск по форуму к сожалению не дал ответа на этот вопрос.

Например гражданин Кодт в одном из постов
Автор: Кодт
Дата: 08.04.13
пишет следующее:

... Правильный взгляд даёт понимание таких вещей, и как ложные пробуждения ...


Но к сожалению не объясняет самой сути этого явления.

Зачем крутиться в цикле и проверять какие-то условия


Пример кода взят с "CodeProject":

#include <thread>
#include <mutex>
#include <condition_variable>
#include <iostream>
#include <queue>
#include <random>

...
std::mutex              g_lockqueue;
std::condition_variable g_queuecheck;
...
bool                    g_notified;



void workerfunc(int id, std::mt19937& generator)
{
    ...
    g_notified = true;
    g_queuecheck.notify_one();
    ...
}

void loggerfunc()
{
    ...
    std::unique_lock<std::mutex> locker(g_lockqueue);

    while(!g_notified) // used to avoid spurious wakeups 
    {
        g_queuecheck.wait(locker);
    }
    ...
}





Надеюсь на содержательные ответы. Всем ответившим заранее спасибо.


П.С.: Ссылки на соответствующие учёные книги приветствуются
условные переменные ложные пробуждения spurious wakeups condition variables
Re[2]: C++11: Синхронизация - Условные переменные и ложные п
От: uzhas Ниоткуда  
Дата: 03.04.15 10:10
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Ещё пример

К>
К>volatile unsigned ready_flag;

К>void update(bool flag)
К>{
К>  ::InterlockedExchange(&ready_flag, flag); // запись атомарная, лочить незачем
К>  cv.notify_one(); // но известить потребителя нужно
К>  // если поменяем на notify_all для второго сценария, - ничего не изменится
К>}

К>void take_ready() // ждёт ready_flag и опускает его
К>{
К>  unique_lock<mutex> l(m); // чисто для протокола - так требует CV
К>  while(!::InterlockedCompareExchange(&ready_flag,false,true)) cv.wait(l);
К>}

К>// первый сценарий
К>void update_nothing() { update(true); update(false); } // дёрнули, даже дважды, но пока поток-приёмник проснётся...

К>// второй сценарий
К>void update_twice() { update(true); update(true); } // дёрнули дважды
К>void thread_one()   { take_ready(); } // первый проснулся и погасил
К>void thread_two()   { take_ready(); } // второй проснулся, а уже погашено
К>


это плохой пример по двум причинам:
1) каша из bool/unsigned. надо на чем-то одном остановиться
2) из-за того, что в void update не лочит мьютекс мы имеем race, из-за которого cv.wait(l) зависнет навечно, хотя ready_flag будет равен true. очень важно понимать, насколько мощен wait:
этот метод одновременно уходит в ожидание и разлочивает мьютекс (транзакционно). это гарантирует, что при правильном использовании (а не как в этом примере) cv, метод notify не уйдет в пустоту. гарантирует, что условие в while не может измениться, пока мы не провалимся в wait. поэтому менять данные, которые могут изменять condition, надо строго под мьютексом.
Отредактировано 03.04.2015 10:56 uzhas . Предыдущая версия . Еще …
Отредактировано 03.04.2015 10:11 uzhas . Предыдущая версия .
Re: C++11: Синхронизация - Условные переменные и ложные пробужде
От: drol  
Дата: 06.04.15 05:56
Оценка:
Здравствуйте, rsdn_179b, Вы писали:

_>Интересует собственно сабж — откуда берутся эти самые "ложные пробуждения" ?


Они берутся из особенностей реализации обработки прерываний\сигналов на целевой платформе. Например в POSIX срабатывание сигнала в ходе ожидания I\O-примитивом "свистка" от устройства прерывает его выполнение со специальным кодом ошибки.

_>Зачем крутиться в цикле и проверять какие-то условия


Потому что в спецификации так написано...
Re: C++11: Синхронизация - Условные переменные и ложные пробужде
От: se_sss  
Дата: 08.04.15 18:31
Оценка:
_>Интересует собственно сабж — откуда берутся эти самые "ложные пробуждения" ?
_>П.С.: Ссылки на соответствующие учёные книги приветствуются.

Ухх! Что-то я про такое и не помнил, хотая документацию Java по wait() читал и там про это написано.
Спасибо.

Возник вопрос не по совсем теме, но всё же связанный с ней.
Сейчас нашёл разъяснение для Linux, состоящее в том, что если идёт блокирующией вызов и приходит сигнал, то вызов прерывается:
http://blog.vladimirprus.com/2005/07/spurious-wakeups.html


Потом вышел на такую страничку(правда немножко не по теме):
http://www.gnu.org/software/libc/manual/html_node/Interrupted-Primitives.html

Здесь предлагают проверять errno на равенство EINTR.

В связи с этой страничкой возник вопрос. А что если у нас несколько потоков? В каком из них ошибка произошла?
errno ведь глобальная переменная?
Re[2]: C++11: Синхронизация - Условные переменные и ложные проб
От: Videoman Россия https://hts.tv/
Дата: 15.03.19 23:01
Оценка:
Здравствуйте, kotalex, Вы писали:

K>...


Спасибо вам большое! Наконец-то человек простым языком, без посылки далеко, объяснил откуда берутся эти "зомби" пробуждения — просто особенность реализации на основе хеш-таблицы, где возможны коллизии при попытки разбудить несколько не связанных CV из разных процессов. Аллилуйя!!!
Re[2]: C++11: Синхронизация - Условные переменные и ложные пробужде
От: Videoman Россия https://hts.tv/
Дата: 15.03.19 23:10
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>Во взятом примере выкинут важный lock вот здесь:

_>>
_>>void workerfunc(int id, std::mt19937& generator)
_>>{
_>>    ...
BFE>        std::unique_lock<std::mutex> locker(g_lockqueue);
_>>    g_notified = true;
_>>    g_queuecheck.notify_one();
_>>    ...
_>>}
_>>

BFE>Изменение g_notified должно быть защищено.

Уж если придираться, то notify_xxx() не обязательно делать под локом, т.к. поток сидящий на wait() все-равно не сможет проснуться, пока вы не покинете область видимости locker-а.
Re[3]: C++11: Синхронизация - Условные переменные и ложные проб
От: Videoman Россия https://hts.tv/
Дата: 18.03.19 11:07
Оценка:
Здравствуйте, watchmaker, Вы писали:

W> ....

W>Призываю не верить объяснению kotalex — оно прикольное, но совершенно неверное.

"Редиска" вы, watchmaker. Все выходные у меня было хорошее настроение и вот опять — мне страшно.
А если серьезно, то все используют механизм, косяки которого научились успешно обходить, и даже все работает как задумывалось, но никто не в состоянии на пальцах объяснить что происходит и почему. Везде только говориться, мол, особенность реализации и в интересах производительности.

To be continued...
Re[4]: C++11: Синхронизация - Условные переменные и ложные проб
От: andyp  
Дата: 18.03.19 16:05
Оценка:
Здравствуйте, Videoman, Вы писали:

V>"Редиска" вы, watchmaker. Все выходные у меня было хорошее настроение и вот опять — мне страшно.

V>А если серьезно, то все используют механизм, косяки которого научились успешно обходить, и даже все работает как задумывалось, но никто не в состоянии на пальцах объяснить что происходит и почему. Везде только говориться, мол, особенность реализации и в интересах производительности.

V>To be continued...


Чем тебя не устраивает объяснение, что cond_wait вылетает при получении процессом сигнала ?

http://blog.vladimirprus.com/2005/07/spurious-wakeups.html

Там пара абзацев про это в конце.
Re[5]: C++11: Синхронизация - Условные переменные и ложные проб
От: Videoman Россия https://hts.tv/
Дата: 18.03.19 19:31
Оценка:
Здравствуйте, andyp, Вы писали:

A>Чем тебя не устраивает объяснение, что cond_wait вылетает при получении процессом сигнала ?


Не совсем устраивает, т.к. не хватает информации в общем. Я так понял:
у Linux все ожидания, всех объектов в ядре так устроены — происходит пробуждение по сигналу со специальным кодом. Т.к. в случаях не связанных с CV у нас состояние объекта присутствует в ядре, мы можем проверить код возврата, перепроверить само состояние и вернуться обратно в ожидание, если оно было ложным. В случае в CV у нас состояние в user mod-е и мы вынуждены вернуться и проверять его самостоятельно. Из всего вышесказанного делаем вывод — futex оказался слишком низкоуровневым объектом что бы его выносить в стандартную библиотеку C++. Ведь никто, слава богу, не догадался сделать spurious_read, spurious_join или spurious_lock. И все-равно остается куча вопросов:

1. Что c Windows ?
2. Если на Windows обошлись без ложных пробуждений, то почему особенности реализации POSIX на Linux "втащили" в стандартную библиотеку ?
3. Почему, например, не сделали флаг для futex-а — NO_SPURIOUS_WAKEUP для использования в высокоуровневом API ?
4. Что с вариантами wait_until и wait_for, ведь если wait_until еще как-то можно в цикле крутить, то wait_for становится весьма нетривиальным ?
Re[6]: C++11: Синхронизация - Условные переменные и ложные проб
От: Sergey_BG Россия  
Дата: 19.03.19 08:51
Оценка:
Здравствуйте, Videoman, Вы писали:
V>4. Что с вариантами wait_until и wait_for, ведь если wait_until еще как-то можно в цикле крутить, то wait_for становится весьма нетривиальным ?
Я в этой теме только первый день, но на cppreferences о wait_for нашёл следующее: "2) Equivalent to return wait_until(lock, std::chrono::steady_clock::now() + rel_time, std::move(pred));. This overload may be used to ignore spurious awakenings."
Сергей
Re[6]: C++11: Синхронизация - Условные переменные и ложные п
От: andyp  
Дата: 19.03.19 10:50
Оценка:
Здравствуйте, Videoman, Вы писали:

V>у Linux все ожидания, всех объектов в ядре так устроены — происходит пробуждение по сигналу со специальным кодом. Т.к. в случаях не связанных с CV у нас состояние объекта присутствует в ядре, мы можем проверить код возврата, перепроверить само состояние и вернуться обратно в ожидание, если оно было ложным. В случае в CV у нас состояние в user mod-е и мы вынуждены вернуться и проверять его самостоятельно.


у Linux cond_wait по любому сигналу, поступающему процессу, отмораживается имхо. Тут должно совпасть следующее:
1.кто-то посигналил процессу
2.обработчик сигнала вызывается в контексте этой нитки

Когда нитка уходит из ожидания в userspace, ей могут посигналить (cond_signal) и сигнал будет пропущен, если окончательно не вывалиться из библиотечного кода и руками не проверить состояние.

V>Из всего вышесказанного делаем вывод — futex оказался слишком низкоуровневым объектом что бы его выносить в стандартную библиотеку C++. Ведь никто, слава богу, не догадался сделать spurious_read, spurious_join или spurious_lock. И все-равно остается куча вопросов:


futex — один из вариантов реализации мьютекса, разве нет?

V>1. Что c Windows ?

V>2. Если на Windows обошлись без ложных пробуждений, то почему особенности реализации POSIX на Linux "втащили" в стандартную библиотеку ?

Говорят, что в винде таки есть:
https://stackoverflow.com/questions/38757420/is-waiting-for-an-event-subject-to-spurious-wakeups

А так, надо в реализацию pthreads для винды глянуть, на чем там мьютексы сделаны. Думаю, на CRITRICAL_SECTION, скорее всего.

V>3. Почему, например, не сделали флаг для futex-а — NO_SPURIOUS_WAKEUP для использования в высокоуровневом API ?


Потому что код обработчика сигнала надо в userspace выполнять всё равно.

V>4. Что с вариантами wait_until и wait_for, ведь если wait_until еще как-то можно в цикле крутить, то wait_for становится весьма нетривиальным ?


Да черт его знает, я с позиксом хоть как-то ковырялся, а на стандартных c++ примитивах кондвары не использовал
Отредактировано 19.03.2019 10:55 andyp . Предыдущая версия .
Re[3]: C++11: Синхронизация - Условные переменные и ложные п
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 21.03.19 21:29
Оценка:
Здравствуйте, uzhas, Вы писали:

U>2) из-за того, что в void update не лочит мьютекс мы имеем race, из-за которого cv.wait(l) зависнет навечно, хотя ready_flag будет равен true. очень важно понимать, насколько мощен wait:

U>этот метод одновременно уходит в ожидание и разлочивает мьютекс (транзакционно). это гарантирует, что при правильном использовании (а не как в этом примере) cv, метод notify не уйдет в пустоту. гарантирует, что условие в while не может измениться, пока мы не провалимся в wait. поэтому менять данные, которые могут изменять condition, надо строго под мьютексом.

Нет. И такое изменение допустимо (см. ниже), и notify за пределами владения мьютексом разрешён во многих подобных реализациях (включая pthreads), и часто даже рекомендуется — за счёт того, что он не вызывает дополнительных переключений (когда задача, получившая нотификацию, пробуждается и тут же засыпает снова на ожидании мьютекса).

Пример Кодта плох не тем, что он мог бы не работать — он работает. Пример плох таки тем, что смешиваются два разных подхода — защита произвольных данных (в общем случае не пригодных к атомарному изменению) мьютексом — как полагается для этого механизма, и игр с interlocked exchange, которые тут требуют дополнительного объяснения — в частности, почему они вообще работают, а тут уже надо углубляться, что InterlockedExchange() содержит в себе полный барьер памяти, который в случае мьютекса реализуется собственно суммой входа в мьютекс и выхода из него.
По-нормальному надо было бы нарисовать что-то вроде кольцевого буфера с head, tail и length — это банальный избитый пример, но он показал бы основы проблемы. А уже после этого пытаться подключать внелоковые атомики.
The God is real, unless declared integer.
Re[2]: C++11: Синхронизация - Условные переменные и ложные пробужде
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 21.03.19 22:01
Оценка:
Здравствуйте, se_sss, Вы писали:

_>Возник вопрос не по совсем теме, но всё же связанный с ней.

_>Сейчас нашёл разъяснение для Linux, состоящее в том, что если идёт блокирующией вызов и приходит сигнал, то вызов прерывается:
_>http://blog.vladimirprus.com/2005/07/spurious-wakeups.html

2005 год? Повторюсь: futex wait сейчас может перезапускаться, так что то обоснование уже неадекватно.
С другой стороны, 1) не линуксом единым, 2) конкретный сигнал может не иметь SA_RESTART, 3) причина может быть не только в сигналах (хотя, если потребитель один, я с ходу не знаю другого источника диверсии).

_>В связи с этой страничкой возник вопрос. А что если у нас несколько потоков? В каком из них ошибка произошла?

_>errno ведь глобальная переменная?

Про это уже ответили — errno она типичная thread local.
The God is real, unless declared integer.
Re[3]: C++11: Синхронизация - Условные переменные и ложные п
От: kotalex  
Дата: 27.03.19 12:58
Оценка:
Здравствуйте, watchmaker , Вы писали:

Вот в ядре linux есть функция match_futex, которая используется во всех операциях обхода бакета как раз для того, чтобы отфильтровать фьютексы попавшие в него из-за коллизий

Да, есть такая функция, которая используется во всех операциях обхода бакета. Можно ссылку на код, который используется именно для отфильтровывания "коллизийных" фьютексов, а не для поиска нужного бакета по ключу ?
Отредактировано 28.03.2019 9:46 kotalex . Предыдущая версия . Еще …
Отредактировано 27.03.2019 13:13 kotalex . Предыдущая версия .
Отредактировано 27.03.2019 13:09 kotalex . Предыдущая версия .
Отредактировано 27.03.2019 13:01 kotalex . Предыдущая версия .
Re[4]: C++11: Синхронизация - Условные переменные и ложные п
От: watchmaker  
Дата: 27.03.19 14:38
Оценка:
Здравствуйте, kotalex, Вы писали:

K>Да, есть такая функция, которая используется во всех операциях обхода бакета. Можно ссылку на код, который используется именно для отфильтровывания "коллизийных" фьютексов, а не для поиска нужного бакета по ключу ?

В ядре linux работа с futex не размазана по куче исходников, а вполне компактно содержится почти целиком в единственном файле. На который уже привёл ссылку в предыдущем сообщении. Пропустил? Ну вот ещё раз: match_futex
А вот чуть ниже в этом же файле пример её целевого использования: https://github.com/torvalds/linux/blob/9e98c678c2d6ae3a17cb2de55d17f69dddaa231b/kernel/futex.c#L1602-L1616
Там неоднократно встречается шаблон из последовательности вызовов:
// получение бакета по фьютексу:
hb = hash_futex(&key);

// обход всех записей в бакете:
plist_for_each_entry_safe(this, next, &hb->chain, list)

// проверка на коллизии
if (match_futex (&this->key, &key))

Везде результаты plist_for_each_entry* отфильтровываются через match_futex.
Re[2]: C++11: Синхронизация - Условные переменные и ложные проб
От: B0FEE664  
Дата: 27.03.19 15:50
Оценка:
Здравствуйте, netch80, Вы писали:

N>Настоящая и исконная причина: в multi-producer-multi-customer построении невозможно гарантировать, что когда потребителю A "свистнули", что появился ресурс, не придёт потребитель B, который захватит мьютекс раньше и потребит ресурс. Когда же B отпустит мьютекс и A получит такую возможность, ему уже может не достаться ресурса, он увидит другое состояние, чем предполагалось на момент отдачи нотификации.


О каком ресурсе (состоянии) идёт речь? У condition_variable нет никакого состояния, кроме одного мьютекса на всех.

N>Если кто-то спросит, почему не делать такую синхронизацию, при которой нет такой проблемы... можно сделать. Например, при notifyOne() указывать, кого именно notify.

Т.е. указывать нить?

N>Но тогда другая проблема — а что, если этот решил вообще выйти из игры?

Это как? У нити нет способа "выйти из игры", она же заблокирована.
И каждый день — без права на ошибку...
Re[3]: C++11: Синхронизация - Условные переменные и ложные п
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 27.03.19 16:00
Оценка:
Здравствуйте, B0FEE664, Вы писали:

N>>Настоящая и исконная причина: в multi-producer-multi-customer построении невозможно гарантировать, что когда потребителю A "свистнули", что появился ресурс, не придёт потребитель B, который захватит мьютекс раньше и потребит ресурс. Когда же B отпустит мьютекс и A получит такую возможность, ему уже может не достаться ресурса, он увидит другое состояние, чем предполагалось на момент отдачи нотификации.


BFE>О каком ресурсе (состоянии) идёт речь?


О том, который защищается данным конкретным мьютексом.

N>>Если кто-то спросит, почему не делать такую синхронизацию, при которой нет такой проблемы... можно сделать. Например, при notifyOne() указывать, кого именно notify.

BFE>Т.е. указывать нить?

Да.

N>>Но тогда другая проблема — а что, если этот решил вообще выйти из игры?

BFE>Это как? У нити нет способа "выйти из игры", она же заблокирована.

С чего вдруг? В данный момент она может, например, быть занята обработкой предыдущего задания
The God is real, unless declared integer.
Отредактировано 28.03.2019 7:00 netch80 . Предыдущая версия .
Re[4]: C++11: Синхронизация - Условные переменные и ложные проб
От: B0FEE664  
Дата: 27.03.19 17:51
Оценка:
Здравствуйте, netch80, Вы писали:

N>>>Настоящая и исконная причина: в multi-producer-multi-customer построении невозможно гарантировать, что когда потребителю A "свистнули", что появился ресурс, не придёт потребитель B, который захватит мьютекс раньше и потребит ресурс. Когда же B отпустит мьютекс и A получит такую возможность, ему уже может не достаться ресурса, он увидит другое состояние, чем предполагалось на момент отдачи нотификации.

BFE>>О каком ресурсе (состоянии) идёт речь?
N>О том, который защищается данной конкретной парой mutex + CV.

Речь идёт о notifyOne()?


N>>>Если кто-то спросит, почему не делать такую синхронизацию, при которой нет такой проблемы... можно сделать. Например, при notifyOne() указывать, кого именно notify.

N>>>Но тогда другая проблема — а что, если этот решил вообще выйти из игры?
BFE>>Это как? У нити нет способа "выйти из игры", она же заблокирована.
N>С чего вдруг? В данный момент она может, например, быть занята обработкой предыдущего задания.

Нить, которая занята обработкой предыдущего задания, пропустит notifyOne() в соответствии с текущей спецификацией.
И каждый день — без права на ошибку...
Re[5]: C++11: Синхронизация - Условные переменные и ложные проб
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 27.03.19 18:45
Оценка:
Здравствуйте, B0FEE664, Вы писали:

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


N>>>>Настоящая и исконная причина: в multi-producer-multi-customer построении невозможно гарантировать, что когда потребителю A "свистнули", что появился ресурс, не придёт потребитель B, который захватит мьютекс раньше и потребит ресурс. Когда же B отпустит мьютекс и A получит такую возможность, ему уже может не достаться ресурса, он увидит другое состояние, чем предполагалось на момент отдачи нотификации.

BFE>>>О каком ресурсе (состоянии) идёт речь?
N>>О том, который защищается данной конкретной парой mutex + CV.
BFE>Речь идёт о notifyOne()?

Нет, здесь — в общем случае.

N>>>>Если кто-то спросит, почему не делать такую синхронизацию, при которой нет такой проблемы... можно сделать. Например, при notifyOne() указывать, кого именно notify.

N>>>>Но тогда другая проблема — а что, если этот решил вообще выйти из игры?
BFE>>>Это как? У нити нет способа "выйти из игры", она же заблокирована.
N>>С чего вдруг? В данный момент она может, например, быть занята обработкой предыдущего задания.
BFE>Нить, которая занята обработкой предыдущего задания, пропустит notifyOne() в соответствии с текущей спецификацией.

И поэтому этот гипотетический метод нужно ещё больше дорабатывать.
The God is real, unless declared integer.
Re[6]: C++11: Синхронизация - Условные переменные и ложные проб
От: B0FEE664  
Дата: 27.03.19 21:27
Оценка:
Здравствуйте, netch80, Вы писали:

BFE>>>>О каком ресурсе (состоянии) идёт речь?

N>>>О том, который защищается данной конкретной парой mutex + CV.
BFE>>Речь идёт о notifyOne()?
N>Нет, здесь — в общем случае.
А причём тут общий случай, если речь идёт о condition_variable ? condition_variable вообще не обязана быть связанной с какими-то разделяемыми данными.

N>>>>>Если кто-то спросит, почему не делать такую синхронизацию, при которой нет такой проблемы... можно сделать. Например, при notifyOne() указывать, кого именно notify.

N>>>>>Но тогда другая проблема — а что, если этот решил вообще выйти из игры?
BFE>>>>Это как? У нити нет способа "выйти из игры", она же заблокирована.
N>>>С чего вдруг? В данный момент она может, например, быть занята обработкой предыдущего задания.
BFE>>Нить, которая занята обработкой предыдущего задания, пропустит notifyOne() в соответствии с текущей спецификацией.
N>И поэтому этот гипотетический метод нужно ещё больше дорабатывать.

В каком смысле дорабатывать? Должна проснутся одна из тех ниток, которые заблокированы, а те, которые работают должны продолжать работать. Это в соответствии с текущей спецификацией.
И каждый день — без права на ошибку...
Re[7]: C++11: Синхронизация - Условные переменные и ложные п
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 28.03.19 06:54
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>>>>>О каком ресурсе (состоянии) идёт речь?

N>>>>О том, который защищается данной конкретной парой mutex + CV.
BFE>>>Речь идёт о notifyOne()?
N>>Нет, здесь — в общем случае.
BFE>А причём тут общий случай, если речь идёт о condition_variable ?

Общий случай для использования condition variable. Я полагал это очевидным по контексту.

BFE> condition_variable вообще не обязана быть связанной с какими-то разделяемыми данными.


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

N>>И поэтому этот гипотетический метод нужно ещё больше дорабатывать.

BFE>В каком смысле дорабатывать? Должна проснутся одна из тех ниток, которые заблокированы, а те, которые работают должны продолжать работать. Это в соответствии с текущей спецификацией.

В первом абзаце исходного сообщения я сказал про текущий режим, а во втором слегка затронул возможные альтернативы (и снова, полагал это отличие очевидным). Но больше я эти альтернативы обсуждать не хочу, потому что обсуждение мгновенно потеряло целостность.
The God is real, unless declared integer.
Отредактировано 28.03.2019 7:17 netch80 . Предыдущая версия .
Re[10]: C++11: Синхронизация - Условные переменные и ложные п
От: B0FEE664  
Дата: 29.03.19 09:23
Оценка:
Здравствуйте, netch80, Вы писали:

BFE>>Поэтому я не вижу никакого прямого способа использования этого примитива, кроме как превратив CV в что-то более логичное, например в объект Event (Событие).

N>Именно что напрямую и используется. Сделать Event на её основе, конечно, можно, но не подходит в заметной части случаев, и излишне в большинстве остальных.

Разве есть что-то такое, что можно сделать на CV и нельзя на Event?
Что же касается излишеств, то экономить на спичках в большинстве случаев не следует.

N>Пусть задача TB закончила выполнение очередной job и стала в очередь на мьютекс. Задача TC поставила новый job в пустую очередь и отпустила мьютекс. TC делает notify_one(), шедулер дёргает проснуться TA (TB сейчас не в ожидании, на неё не поставят). TA становится в очередь на мьютекс. TC отпускает мьютекс (до notify_one() или после — почти без разницы), TB захватывает мьютекс, начинает выполняться, забирает задание, очередь становится пустой. TA приходит за заданием, а его нет — TB забрала раньше. TA идёт спать на новый круг.


Это описание выглядит как описание типичной ошибки race condition: два независимых события 'пробудить очередь' и 'отдать-захватить мьютекс' не синхронизированы.

N>Оптимизировав логику шедулера, можно сократить частоту таких левых пробуждений, но свести до нуля — не получается. А ещё есть случаи старта новых задач в пул, и с ними надо тоже синхронизироваться.


Я не верю, что задача является неразрешимой.
И каждый день — без права на ошибку...
Re[11]: C++11: Синхронизация - Условные переменные и ложные п
От: watchmaker  
Дата: 29.03.19 10:12
Оценка:
Здравствуйте, B0FEE664, Вы писали:

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


N>>Пусть задача TB закончила выполнение очередной job и стала в очередь на мьютекс. Задача TC поставила новый job в пустую очередь и отпустила мьютекс. TC делает notify_one(), шедулер дёргает проснуться TA (TB сейчас не в ожидании, на неё не поставят). TA становится в очередь на мьютекс. TC отпускает мьютекс (до notify_one() или после — почти без разницы), TB захватывает мьютекс, начинает выполняться, забирает задание, очередь становится пустой. TA приходит за заданием, а его нет — TB забрала раньше. TA идёт спать на новый круг.


BFE>Это описание выглядит как описание типичной ошибки race condition: два независимых события 'пробудить очередь' и 'отдать-захватить мьютекс' не синхронизированы.

Конкретно в этом примере нет гонки с пробуждением. Он же позвал notify_one внутри захвата мьютекса. Ядро в ответ на вызов notify_one тут же будит поток (который попадает в планировщик), и лишь потом отпускает мьютекс. Что это тогда, если не самая лучшая синхронизация? :)

Если другая проблема: потоки могут тупить по сотне разных причин. То им процессора не достанется, или достанется, но их другой поток тут же вытеснит, то page fault случится. В результате задание из очереди получает более расторопный поток (и это хорошо). И по большому счёту эта ситуация эквивалентна другой ситуации, в которой нет никаких условных переменных и сигналов пробуждения, а просто есть два потока-исполнителя стартующих одновременно и единственное задание в очереди. Никто же не называет гонкой ситуацию, что задание может достаться одному исполнителю или другому:
std::vector<int> v = {{1}};
std::for_each(std::execution::parallel_policy, begin(v), end(v), fn);

— не гонка, что fn будет вызван в случайном потоке из пула.
Re[12]: C++11: Синхронизация - Условные переменные и ложные
От: B0FEE664  
Дата: 29.03.19 10:46
Оценка:
Здравствуйте, watchmaker, Вы писали:

BFE>>Это описание выглядит как описание типичной ошибки race condition: два независимых события 'пробудить очередь' и 'отдать-захватить мьютекс' не синхронизированы.

W>Конкретно в этом примере нет гонки с пробуждением. Он же позвал notify_one внутри захвата мьютекса. Ядро в ответ на вызов notify_one тут же будит поток (который попадает в планировщик), и лишь потом отпускает мьютекс. Что это тогда, если не самая лучшая синхронизация?

Уточню: гонка тут не со стороны приведённого кода, а со стороны системы.

W>Если другая проблема: потоки могут тупить по сотне разных причин. То им процессора не достанется, или достанется, но их другой поток тут же вытеснит, то page fault случится. В результате задание из очереди получает более расторопный поток (и это хорошо). И по большому счёту эта ситуация эквивалентна другой ситуации, в которой нет никаких условных переменных и сигналов пробуждения, а просто есть два потока-исполнителя стартующих одновременно и единственное задание в очереди. Никто же не называет гонкой ситуацию, что задание может достаться одному исполнителю или другому:

W>
W>std::vector<int> v = {{1}};
W>std::for_each(std::execution::parallel_policy, begin(v), end(v), fn);
W>

W>— не гонка, что fn будет вызван в случайном потоке из пула.

Если fn будет вызвана дважды для одного и того же итератора в одном из этих случайных потоков, то это состояние гонки.
И каждый день — без права на ошибку...
Отредактировано 29.03.2019 10:49 B0FEE664 . Предыдущая версия .
Re[11]: C++11: Синхронизация - Условные переменные и ложные п
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 30.03.19 06:53
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>>>Поэтому я не вижу никакого прямого способа использования этого примитива, кроме как превратив CV в что-то более логичное, например в объект Event (Событие).

N>>Именно что напрямую и используется. Сделать Event на её основе, конечно, можно, но не подходит в заметной части случаев, и излишне в большинстве остальных.

BFE>Разве есть что-то такое, что можно сделать на CV и нельзя на Event?


Я не сказал "нельзя", я сказал "не подходит"
В смысле, будет сильно неудобно — в рамках того подхода, в котором используются CV.
Насколько "нельзя", я не в курсе.

BFE>Что же касается излишеств, то экономить на спичках в большинстве случаев не следует.


Пока что вы действуете чисто по вкусовщине "мне не нравятся CV, я их не хочу понимать, все бегом на Event". Я вообще-то не собирался это глубоко обсуждать, но если уж идём в эту сторону...
покажите такой же thread pool, как я постил, на Event, и расскажите детально, почему он работает и почему он надёжно работает. Хотя лучше с этим куда-то за пределы субфорума C++. Например, в "Алгоритмы".

Никто не говорит, что подход с CV единственный или заведомо идеальный, но если не видно массового перехода на другой подход, то наверняка у него не столь фатальны отрицательные стороны, как вам кажется.
(Это я не вспоминаю, что в рамках стандарта C++ у нас пока в принципе нет альтернативы.)

N>>Оптимизировав логику шедулера, можно сократить частоту таких левых пробуждений, но свести до нуля — не получается. А ещё есть случаи старта новых задач в пул, и с ними надо тоже синхронизироваться.


BFE>Я не верю, что задача является неразрешимой.


Над этой темой думают сотни учёных в мире. Если с середины 60-х ничего не придумали, то, наверно, это не так просто, как вам кажется.
The God is real, unless declared integer.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.