2013-04-08 4 views
13

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

Но, я осмотрел 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++?

ответ

13

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

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

  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.

+0

Спасибо! Это очень помогло мне. Эсперично ответьте на 2 и 3 - хороший момент об атомарности и внутреннем материале, который все равно использует мьютекс. Кстати, вы можете добавить ссылку на pthread_cond_wait http://pubs.opengroup.org/onlinepubs/7908799/xsh/pthread_cond_wait.html, чтобы показать, что наиболее распространенная реализация работает только с ее собственным мьютексом во внутренних компонентах. Не могли бы вы прояснить, что именно вы имеете в виду: «Там есть довольно тонкий код». – qble

+0

Относительно 1. - может ли незаблокирован условие_вариабельный :: notify_one привести к пропущенным уведомлениям? То есть thread # 1 делает работу под мьютексом, посылает результат, разблокирует мьютекс, [тем временем] поток # 2 блокирует мьютекс, но еще не вызвал wait, [тем временем] поток # 1 вызывает notify_one, [тем временем] поток # 2 вызывает wait - уведомление теряется. – qble

+0

Это не потерянное уведомление, это ожидающий поток, не проверяющий предикат условия перед ожиданием. Блокировка мьютекса не помогает этой ситуации, уведомление все равно может появиться до того, как ожидающий поток заблокирует мьютекс. Вы ** должны ** проверять связанный предикат при ожидании переменной состояния –

2

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

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

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

+0

Возможно, я должен добавить к Q: реализация condition_variable_any - это просто тонкая оболочка вокруг condition_variable + mutex. – qble

+0

«Просто потому, что ваша программа может их терпеть, не означает, что могут использовать произвольные пользователи библиотеки, и, в общем, я ожидаю, что они не смогут» - я думаю, вы не понимаете, если я не защищу свой notify_one мьютекс сам по себе - могут быть потеряны уведомления, но если я его охраняю - они должны быть невозможны. Реализация не должна заботиться о потерянных уведомлениях, когда пользователь не защищает notify_one с помощью мьютекса. – qble

+0

@qble: Опять же, я не могу прокомментировать то, что решает разработчик стандартной библиотеки, чтобы реализовать их реализацию стандарта. Им разрешено использовать любую магию компилятора, чтобы заставить ее работать (в том числе неопределенное поведение, если они знают, что их платформа определяет ее), и они могут использовать очень специфические знания. Может быть, они знают, что std :: lock_guard действует достаточно атомарно для них, но типы, связанные с параметрами шаблона, могут и не быть. Вы можете найти ключ к их реализации wait(). –

Смежные вопросы