C ++ 11: чому std :: condition_variable використовує std :: unique_lock?


74

Я трохи заплутаний у ролі, 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оптимізації? Якщо так, то як саме це швидше?


5
Вас бентежить, навіщо вам потрібен замок / мьютекс із змінною умови, або різниця між блокуванням та мьютексом, або чому змінна умови використовує унікальний замок, а не мьютекс?
— Брейді,

1
"чому змінна умови використовує унікальний замок, а не мьютекс" це.
— Лукас Клементе,

Відповіді:


104

тож немає технічної причини?

Я підтримав відповідь 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_variableAPI, який приймає 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
}
  1. Цей код настільки простий, наскільки він може отримати.
  2. Це виняток безпечний.
  3. 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це такий інструмент низького рівня, ця дуже корисна, але дорожча функціональність була виділена в окремий клас, так що ви платите за неї, лише якщо користуєтесь нею.


Не могли б ви пояснити "це". Ви говорите про lock_guard, чи condition_variable, чи, можливо condition_variable::wait?
— Говард Хіннант,

Вибач, забудь. Я зовсім забув проcondition_variable_any
— Стефана Доллберга

<nod> Ах, шаблон condition_variable::wait. Так, condition_variable_anyце шлях. І причина того, що функціональність не складена, condition_variableполягає в тому, що вона дорожча. І condition_variableце такий інструмент низького рівня, він повинен був бути якомога ефективнішим. Ви платите за додану функціональність, лише якщо використовуєте condition_variable_any.
— Говард Хіннант,

Добре, дякую за додаткову інформацію. Можливо, додайте це до своєї відповіді, оскільки можна неправильно використати condition_variable_anyзвичайний мьютекс.
— Stephan Dollberg

5
Ого, мене вразила ваша відповідь. Велике спасибі за цей рівень деталізації!
— lucas clemente

35

По суті, це рішення щодо дизайну API, щоб зробити API максимально безпечним за замовчуванням (при цьому додаткові накладні витрати вважаються незначними). Вимагаючи передавати unique_lockзамість необробленого, mutexкористувачі API спрямовуються на написання правильного коду (за наявності винятків).

Останніми роками фокус мови С ++ змістився на те, щоб зробити його безпечним за замовчуванням (але все одно дозволяючи користувачам забивати собі ноги, якщо вони цього хочуть, і намагатися достатньо).


тож немає технічної причини?
— Лукас Клементе,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.