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

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

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

2 ответов


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

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

  1. является ли потокобезопасным не защищать notify_one с помощью мьютекса для condition_variable_any или condition_variable?

да.

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

если вы называете notify_one() без заблокированного мьютекса потоки пробуждения могут запускаться немедленно.

2 почему реализация condition_variable_any использует дополнительные мьютекс?

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