Ответ 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
- такой инструмент низкого уровня, эта очень полезная, но более дорогая функциональность была помещена в отдельный класс, так что вы платите только за нее, если вы используете его.