C ++ 11: Warum verwendet std :: condition_variable std :: unique_lock?

74

Ich bin etwas verwirrt über die Rolle std::unique_lockbei der Arbeit mit std::condition_variable. Soweit ich die Dokumentation verstanden habe , std::unique_lockhandelt es sich im Grunde genommen um einen aufgeblähten Schlossschutz mit der Möglichkeit, den Zustand zwischen zwei Schlössern auszutauschen.

Ich habe bisher pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)für diesen Zweck verwendet (ich denke, das ist, was die STL auf posix verwendet). Es braucht einen Mutex, kein Schloss.

Was ist der Unterschied hier? Geht es std::condition_variableum std::unique_lockeine Optimierung? Wenn ja, wie genau ist es schneller?

lucas clemente
quelle
5
Sind Sie verwirrt darüber, warum Sie eine Sperre / einen Mutex mit einer Bedingungsvariablen benötigen oder über den Unterschied zwischen einer Sperre und einem Mutex oder warum eine Bedingungsvariable eine eindeutige Sperre und keinen Mutex verwendet?
Brady
1
"Warum eine Bedingungsvariable eine eindeutige Sperre und keinen Mutex verwendet".
Lucas Clemente

Antworten:

104

Es gibt also keinen technischen Grund?

Ich habe die Antwort von cmeerw positiv bewertet, weil ich glaube, dass er einen technischen Grund angegeben hat. Lass uns durchgehen. Stellen wir uns vor, das Komitee hätte beschlossen, auf einen zu condition_variablewarten mutex. Hier ist Code mit diesem Design:

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

Genau so sollte man a nicht benutzen condition_variable. In den mit:

// mut locked by this thread here

Es gibt ein Ausnahme-Sicherheitsproblem, und es ist ein ernstes. Wenn in diesen Bereichen (oder für sich cv.waitallein) eine Ausnahme ausgelöst wird , ist der gesperrte Status des Mutex durchgesickert, es sei denn, irgendwo wird ein Versuch / Fang eingefügt, um die Ausnahme abzufangen und zu entsperren. Aber das ist nur mehr Code, den der Programmierer schreiben soll.

Angenommen, der Programmierer weiß, wie ausnahmesicherer Code geschrieben und verwendet wird unique_lock, um dies zu erreichen. Jetzt sieht der Code so aus:

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
}

Das ist viel besser, aber es ist immer noch keine großartige Situation. Die condition_variableBenutzeroberfläche lässt den Programmierer alles daran setzen, die Dinge zum Laufen zu bringen. Es gibt eine mögliche Nullzeiger-Dereferenzierung, wenn lkversehentlich kein Mutex referenziert wird. Und es gibt keine Möglichkeit condition_variable::waitzu überprüfen, ob dieser Thread die Sperre besitzt mut.

Oh, nur daran erinnert, es besteht auch die Gefahr, dass der Programmierer die falsche unique_lockElementfunktion wählt , um den Mutex freizulegen. *lk.release()wäre hier katastrophal.

Schauen wir uns nun an, wie der Code mit der tatsächlichen condition_variableAPI geschrieben wird, die Folgendes benötigt 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. Dieser Code ist so einfach wie möglich.
  2. Es ist ausnahmesicher.
  3. Die waitFunktion kann lk.owns_lock()eine Ausnahme prüfen und auslösen, wenn dies der Fall ist false.

Dies sind technische Gründe, die das API-Design von vorangetrieben haben condition_variable.

Zusätzlich condition_variable::waitnimmt eine nicht lock_guard<mutex>da lock_guard<mutex>ist , wie Sie sagen: Ich besitze die Sperre auf diesem Mutex , bis lock_guard<mutex>zerstört. Wenn Sie jedoch anrufen condition_variable::wait, lösen Sie implizit die Sperre für den Mutex. Diese Aktion stimmt also nicht mit dem lock_guardAnwendungsfall / der Anweisung überein.

Wir brauchten unique_locksowieso, damit man Sperren von Funktionen zurückgeben, sie in Container packen und Mutexe in Mustern ohne Gültigkeitsbereich auf ausnahmesichere Weise sperren / entsperren konnte, also unique_lockwar dies die natürliche Wahl für condition_variable::wait.

Aktualisieren

Bambus schlug in den Kommentaren unten vor, dass ich kontrastiere condition_variable_any, also hier geht's:

Frage: Warum wird keine condition_variable::waitVorlage erstellt, damit ich einen beliebigen LockableTyp an ihn übergeben kann?

Antworten:

Das ist wirklich coole Funktionalität. In diesemshared_lock Artikel wird beispielsweise Code demonstriert, der auf einen (rwlock) im gemeinsam genutzten Modus auf eine Bedingungsvariable wartet (etwas, das in der Posix-Welt unbekannt ist, aber dennoch sehr nützlich). Die Funktionalität ist jedoch teurer.

Deshalb hat das Komitee einen neuen Typ mit dieser Funktionalität eingeführt:

`condition_variable_any`

Mit diesem condition_variable Adapter kann man auf jeden abschließbaren Typ warten . Wenn es Mitglieder hat lock()und unlock(), sind Sie gut zu gehen. Eine ordnungsgemäße Implementierung von condition_variable_anyerfordert ein condition_variableDatenelement und ein shared_ptr<mutex>Datenelement.

Da diese neue Funktionalität teurer ist als Ihre Basisfunktionalität condition_variable::waitund ein condition_variableso einfaches Tool ist, wurde diese sehr nützliche, aber teurere Funktionalität in eine separate Klasse eingeteilt, sodass Sie nur dann dafür bezahlen, wenn Sie sie verwenden.

Howard Hinnant
quelle
Könnten Sie "es" klarstellen. Sprechen Sie über lock_guardoder condition_variableoder vielleicht condition_variable::wait?
Howard Hinnant
Entschuldigung, vergiss es. Ich habe es total vergessencondition_variable_any
Stephan Dollberg
<nicken> Ah, Vorlage condition_variable::wait. Ja, condition_variable_anyist der richtige Weg. Und der Grund, warum Funktionalität nicht integriert condition_variableist, ist, dass sie teurer ist. Und es condition_variableist ein so einfaches Werkzeug, dass es so effizient wie möglich sein muss. Sie zahlen nur für die hinzugefügte Funktionalität, wenn Sie diese verwenden condition_variable_any.
Howard Hinnant
Ok, danke für die zusätzlichen Infos. Vielleicht fügen Sie das Ihrer Antwort hinzu, da man es condition_variable_anymit einem einfachen Mutex missbrauchen könnte .
Stephan Dollberg
5
Wow, ich bin überwältigt von deiner Antwort. Vielen Dank für diese Detailgenauigkeit!
Lucas Clemente
35

Es ist im Wesentlichen eine Entscheidung des API-Designs, die API standardmäßig so sicher wie möglich zu machen (wobei der zusätzliche Overhead als vernachlässigbar angesehen wird). Durch das Erfordernis, ein unique_lockanstelle eines unformatierten mutexBenutzers der API zu übergeben, wird darauf hingewiesen, korrekten Code zu schreiben (bei Vorhandensein von Ausnahmen).

In den letzten Jahren hat sich der Fokus der C ++ - Sprache darauf verlagert, sie standardmäßig sicher zu machen (aber es den Benutzern immer noch zu ermöglichen, sich selbst in die Füße zu schießen, wenn sie wollen und sich genug anstrengen).

cmeerw
quelle
Es gibt also keinen technischen Grund?
Lucas Clemente