Здравствуйте, 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 идёт спать на новый круг.
Оптимизировав логику шедулера, можно сократить частоту таких левых пробуждений, но свести до нуля — не получается. А ещё есть случаи старта новых задач в пул, и с ними надо тоже синхронизироваться.