Подтвердить что ты не робот

С++ 11: почему std:: condition_variable использует std:: unique_lock?

Я немного смущен о роли std::unique_lock при работе с std::condition_variable. Насколько я понял документацию , std::unique_lock - это в основном раздутый замок, с возможностью обмена состояния между двумя замками.

Я до сих пор использовал pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) для этой цели (я думаю, что STL использует в posix). Он принимает мьютекс, а не блокировку.

Какая разница здесь? Является ли тот факт, что std::condition_variable имеет дело с std::unique_lock оптимизацией? Если да, то как именно быстрее?

4b9b3361

Ответ 1

поэтому нет технической причины?

Я поддержал ответ cmeerw, потому что я считаю, что он дал техническую причину. Позвольте пройти через него. Предположим, что комитет решил провести condition_variable на mutex. Вот код с использованием этого дизайна:

void foo()
{
    mut.lock();
    // mut locked by this thread here
    while (not_ready)
        cv.wait(mut);
    // mut locked by this thread here
    mut.unlock();
}

Именно так нельзя использовать condition_variable. В регионах, отмеченных:

// mut locked by this thread here

существует проблема безопасности исключений, и она серьезная. Если в этих областях выбрано исключение (или только cv.wait), заблокированное состояние мьютекса просачивается, если попытка try/catch также не помещается где-нибудь, чтобы поймать исключение и разблокировать его. Но это еще один код, который вы просите программиста написать.

Скажем, что программист знает, как писать безопасный код исключения, и знает, как использовать unique_lock для его достижения. Теперь код выглядит следующим образом:

void foo()
{
    unique_lock<mutex> lk(mut);
    // mut locked by this thread here
    while (not_ready)
        cv.wait(*lk.mutex());
    // mut locked by this thread here
}

Это намного лучше, но это еще не очень хорошая ситуация. Интерфейс condition_variable заставляет программиста уйти с пути, чтобы заставить работать. Существует возможное разыменование нулевого указателя, если lk случайно не ссылается на мьютекс. И нет способа condition_variable::wait проверить, что этот поток имеет блокировку на mut.

О, просто помните, существует также опасность того, что программист может выбрать неправильную функцию-член unique_lock, чтобы выставить мьютекс. *lk.release() будет катастрофическим здесь.

Теперь посмотрим, как код написан с фактическим condition_variable API, который принимает unique_lock<mutex>:

void foo()
{
    unique_lock<mutex> lk(mut);
    // mut locked by this thread here
    while (not_ready)
        cv.wait(lk);
    // mut locked by this thread here
}
  • Этот код так же прост, как он может получить.
  • Это безопасно.
  • Функция wait может проверять lk.owns_lock() и вызывать исключение, если оно false.

Это технические причины, по которым дизайн API был condition_variable.

Кроме того, condition_variable::wait не принимает lock_guard<mutex>, потому что lock_guard<mutex> - это то, как вы говорите: у меня есть блокировка этого мьютекса до тех пор, пока lock_guard<mutex> не уничтожит. Но когда вы вызываете condition_variable::wait, вы неявно освобождаете блокировку мьютекса. Таким образом, действие несовместимо с оператором использования lock_guard.

В любом случае нам понадобилось unique_lock, чтобы можно было возвращать блокировки из функций, помещать их в контейнеры и блокировать/разблокировать мьютексы в шаблонах без видимых областей безопасным способом, поэтому unique_lock был естественным выбором для condition_variable::wait.

Обновление

bamboon предложил в комментариях ниже, что я контрастирую condition_variable_any, поэтому здесь идет:

Вопрос: Почему шаблон condition_variable::wait не подходит, поэтому я могу передать ему любой тип Lockable?

Ответ:

Это действительно классная функциональность. Например, в этой статье демонстрируется код, ожидающий на shared_lock (rwlock) в режиме совместного использования при переменной состояния (что-то неслыханное в мире posix, но очень полезен, тем не менее). Однако функциональность более дорогая.

Таким образом, комитет представил новый тип с этой функциональностью:

`condition_variable_any`

С помощью этого адаптера condition_variable можно подождать по любому заблокируемому типу. Если у него есть члены lock() и unlock(), вам хорошо идти. Для правильной реализации condition_variable_any требуется элемент данных condition_variable и элемент данных shared_ptr<mutex>.

Поскольку эта новая функциональность более дорогая, чем ваша базовая condition_variable::wait, и потому что condition_variable - такой инструмент низкого уровня, эта очень полезная, но более дорогая функциональность была помещена в отдельный класс, так что вы платите только за нее, если вы используете его.

Ответ 2

Это, по сути, решение для проектирования API, чтобы сделать API как можно безопаснее по умолчанию (при этом дополнительные служебные данные рассматриваются как незначительные). Требование передать unique_lock вместо необработанных mutex пользователей API направлено на создание правильного кода (при наличии исключений).

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