Нужно ли синхронизировать std :: condition_variable / condition_variable_any :: notify_one

Нужно ли синхронизировать 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.

Итак, вопросы:

  1. Это потокобезопасный, чтобы не защищать notify_one мьютексом либо condition_variable_any или же condition_variable?
  2. Почему реализация condition_variable_any использует дополнительный мьютекс?
  3. Почему реализация condition_variable_any::notify_one а также condition_variable::notify_one отличается? Может быть condition_variable::notify_one требует ручной защиты, но condition_variable_any::notify_one не делает? Это ошибка libstdc ++?

15

Решение

То есть это просто тонкая оболочка вокруг condition_variable + mutex.

Э, нет Только потому, что у него есть члены тех типов, это не делает его тонкой оберткой. Попытайтесь понять, что он на самом деле делает, а не только типы его частных членов. Там есть довольно тонкий код.

  1. Является ли потокобезопасным, чтобы не защищать 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,

14

Другие решения

(1) Я не вижу никакой причины, по которой сигнальная переменная условия должна быть защищена мьютексом с точки зрения гонки данных. Очевидно, у вас есть возможность получать избыточные уведомления или потерять уведомления, но если это допустимое или исправимое условие ошибки для вашей программы, я не верю, что в стандарте есть что-то, что сделает его незаконным. Стандарт, конечно, не защитит вас от условий гонки; ответственность за обеспечение благоприятных условий гонки лежит на программисте. (И, конечно, очень важно, чтобы программист не включал «гонки данных», которые очень конкретно определены в стандарте, но не применяются непосредственно к примитивам синхронизации, или вызывается неопределенное поведение.)

(2) Я не могу ответить на такой вопрос о внутренней реализации стандартной библиотеки. Это, конечно, обязанность поставщика предоставить библиотечные средства, которые работают правильно и соответствуют спецификации. Реализация этой библиотеки может иметь некоторое внутреннее состояние, которое требует взаимного исключения, чтобы избежать повреждения, или она может выполнять блокировку, чтобы избежать потерянных или избыточных уведомлений. (То, что ваша программа может их терпеть, не означает, что произвольные пользователи библиотеки могут, и в целом я ожидаю, что они не смогут.) С моей стороны было бы предположение, что они защищают этим мьютексом.

(3) condition_variable_any предназначен для работы на любом подобном замку объекте, а condition_variable разработан специально для работы с unique_lock<mutex>, Последний, вероятно, легче реализовать и / или более производительный, чем первый, поскольку он точно знает, над какими типами он работает и для чего он нужен (являются ли они тривиальными, помещаются ли они в строку кэша, отображаются ли они непосредственно на набор системных вызовов конкретной платформы, то, что гарантируют ограждения или согласованность кэша, и т. д.), в то время как первая предоставляет универсальное средство для работы с объектами блокировки, не привязываясь конкретно к ограничениям std::mutex или же std::unique_lock<>,

2

По вопросам рекламы [email protected]