Нужно ли синхронизировать std::condition_variable/condition_variable_any::notify_one
?
Насколько я вижу, если потеря уведомлений приемлема — это нормально, чтобы позвонить notify_one
не защищен (например, мьютексом).
Например, я видел следующие шаблоны использования (извините, не помню где):
{
{
lock_guard<mutex> l(m);
// do work
}
c.notify_one();
}
Но я проверил источники libstdc ++ и вижу:
condition_variable :: notify_one
void condition_variable::notify_one() noexcept
{
int __e = __gthread_cond_signal(&_M_cond);
// XXX not in spec
// EINVAL
if (__e)
__throw_system_error(__e);
}
а также condition_variable_any :: notify_one:
void condition_variable_any::notify_one() noexcept
{
lock_guard<mutex> __lock(_M_mutex);
_M_cond.notify_one();
}
А вот макет condition_variable_any:
class condition_variable_any
{
condition_variable _M_cond;
mutex _M_mutex;
// data end
То есть это просто тонкая оболочка вокруг condition_variable + mutex.
Итак, вопросы:
notify_one
мьютексом либо condition_variable_any
или же condition_variable
?condition_variable_any::notify_one
а также condition_variable::notify_one
отличается? Может быть condition_variable::notify_one
требует ручной защиты, но condition_variable_any::notify_one
не делает? Это ошибка libstdc ++?То есть это просто тонкая оболочка вокруг condition_variable + mutex.
Э, нет Только потому, что у него есть члены тех типов, это не делает его тонкой оберткой. Попытайтесь понять, что он на самом деле делает, а не только типы его частных членов. Там есть довольно тонкий код.
- Является ли потокобезопасным, чтобы не защищать notify_one от мьютекса ни для condition_variable_any, ни для condition_variable?
Да.
На самом деле, зовет notify_one()
с заблокированным мьютексом вызовет ожидание потоков, попытка заблокировать мьютекс, обнаружить, что он все еще заблокирован уведомляющим потоком, и вернуться в режим сна, пока мьютекс не будет освобожден.
Если вы позвоните notify_one()
без блокировки мьютекса можно сразу же запустить бодрствующие потоки.
2 Почему реализация condition_variable_any использует дополнительный мьютекс?
condition_variable_any
можно использовать с любым Lockable типа, а не только std:mutex
, но внутренне тот, что в libstdc ++ использует condition_variable
, который может быть использован только с std::mutex
так что у него есть внутренний std::mutex
объект тоже.
Итак condition_variable_any
работает с двумя мьютексами: внешним, предоставленным пользователем, и внутренним, используемым реализацией.
3 Чем отличается реализация condition_variable_any :: notify_one и condition_variable :: notify_one? Может быть, condition_variable :: notify_one требует ручной защиты, а условие condition_variable_any :: notify_one — нет? Это ошибка libstdc ++?
Нет, это не ошибка.
Стандарт требует, чтобы вызов wait(mx)
должен атомно разблокировать mx
и спать. libstdc ++ использует внутренний мьютекс для обеспечения этой гарантии атомарности. Внутренний мьютекс должен быть заблокирован, чтобы избежать пропущенных уведомлений, если другие потоки просто ожидают condition_variable_any
,
(1) Я не вижу никакой причины, по которой сигнальная переменная условия должна быть защищена мьютексом с точки зрения гонки данных. Очевидно, у вас есть возможность получать избыточные уведомления или потерять уведомления, но если это допустимое или исправимое условие ошибки для вашей программы, я не верю, что в стандарте есть что-то, что сделает его незаконным. Стандарт, конечно, не защитит вас от условий гонки; ответственность за обеспечение благоприятных условий гонки лежит на программисте. (И, конечно, очень важно, чтобы программист не включал «гонки данных», которые очень конкретно определены в стандарте, но не применяются непосредственно к примитивам синхронизации, или вызывается неопределенное поведение.)
(2) Я не могу ответить на такой вопрос о внутренней реализации стандартной библиотеки. Это, конечно, обязанность поставщика предоставить библиотечные средства, которые работают правильно и соответствуют спецификации. Реализация этой библиотеки может иметь некоторое внутреннее состояние, которое требует взаимного исключения, чтобы избежать повреждения, или она может выполнять блокировку, чтобы избежать потерянных или избыточных уведомлений. (То, что ваша программа может их терпеть, не означает, что произвольные пользователи библиотеки могут, и в целом я ожидаю, что они не смогут.) С моей стороны было бы предположение, что они защищают этим мьютексом.
(3) condition_variable_any
предназначен для работы на любом подобном замку объекте, а condition_variable
разработан специально для работы с unique_lock<mutex>
, Последний, вероятно, легче реализовать и / или более производительный, чем первый, поскольку он точно знает, над какими типами он работает и для чего он нужен (являются ли они тривиальными, помещаются ли они в строку кэша, отображаются ли они непосредственно на набор системных вызовов конкретной платформы, то, что гарантируют ограждения или согласованность кэша, и т. д.), в то время как первая предоставляет универсальное средство для работы с объектами блокировки, не привязываясь конкретно к ограничениям std::mutex
или же std::unique_lock<>
,