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

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

Насколько я могу судить, если потеряны уведомления допустимы - нормально позвонить notify_one не защищен (например, mutex).

Например, я видел следующие шаблоны использования (извините, не помню где):

{
    {
        lock_guard<mutex> l(m);
        // do work
    }
    c.notify_one();
}

Но я проверял источники libstdС++, и я вижу:

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 отключать mutex для condition_variable_any или condition_variable?
  • Почему реализация condition_variable_any использует дополнительный мьютекс?
  • Почему реализация condition_variable_any::notify_one и condition_variable::notify_one отличается? Может быть, condition_variable::notify_one требует ручной защиты, но condition_variable_any::notify_one нет? Это libstdС++ ошибка?

Ответ 1

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

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

  • Не потокобезопасно ли защищать notify_one посредством mutex для condition_variable_any или condition_variable?

Да.

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

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

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

condition_variable_any может использоваться с любым типом Lockable, а не только с std:mutex, но внутри него в libstdС++ используется 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 нет? Это ошибка libstdС++?

Нет, это не ошибка.

Стандарт требует, чтобы вызов wait(mx) должен был автоматически разблокировать mx и спать. libstdС++ использует внутренний мьютекс, чтобы обеспечить эту гарантию атомарности. Внутренний мьютекс должен быть заблокирован, чтобы избежать пропущенных уведомлений, если другие потоки вот-вот ожидают condition_variable_any.

Ответ 2

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

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

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