Ich bin etwas verwirrt über die Rolle std::unique_lock
bei der Arbeit mit std::condition_variable
. Soweit ich die Dokumentation verstanden habe , std::unique_lock
handelt 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_variable
um std::unique_lock
eine Optimierung? Wenn ja, wie genau ist es schneller?
c++
multithreading
c++11
mutex
lucas clemente
quelle
quelle
Antworten:
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_variable
wartenmutex
. 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.wait
allein) 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_variable
Benutzeroberfläche lässt den Programmierer alles daran setzen, die Dinge zum Laufen zu bringen. Es gibt eine mögliche Nullzeiger-Dereferenzierung, wennlk
versehentlich kein Mutex referenziert wird. Und es gibt keine Möglichkeitcondition_variable::wait
zu überprüfen, ob dieser Thread die Sperre besitztmut
.Oh, nur daran erinnert, es besteht auch die Gefahr, dass der Programmierer die falsche
unique_lock
Elementfunktion 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_variable
API geschrieben wird, die Folgendes benötigtunique_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
Funktion kannlk.owns_lock()
eine Ausnahme prüfen und auslösen, wenn dies der Fall istfalse
.Dies sind technische Gründe, die das API-Design von vorangetrieben haben
condition_variable
.Zusätzlich
condition_variable::wait
nimmt eine nichtlock_guard<mutex>
dalock_guard<mutex>
ist , wie Sie sagen: Ich besitze die Sperre auf diesem Mutex , bislock_guard<mutex>
zerstört. Wenn Sie jedoch anrufencondition_variable::wait
, lösen Sie implizit die Sperre für den Mutex. Diese Aktion stimmt also nicht mit demlock_guard
Anwendungsfall / der Anweisung überein.Wir brauchten
unique_lock
sowieso, damit man Sperren von Funktionen zurückgeben, sie in Container packen und Mutexe in Mustern ohne Gültigkeitsbereich auf ausnahmesichere Weise sperren / entsperren konnte, alsounique_lock
war dies die natürliche Wahl fürcondition_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::wait
Vorlage erstellt, damit ich einen beliebigenLockable
Typ an ihn übergeben kann?Antworten:
Das ist wirklich coole Funktionalität. In diesem
shared_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:
Mit diesem
condition_variable
Adapter kann man auf jeden abschließbaren Typ warten . Wenn es Mitglieder hatlock()
undunlock()
, sind Sie gut zu gehen. Eine ordnungsgemäße Implementierung voncondition_variable_any
erfordert eincondition_variable
Datenelement und einshared_ptr<mutex>
Datenelement.Da diese neue Funktionalität teurer ist als Ihre Basisfunktionalität
condition_variable::wait
und eincondition_variable
so 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.quelle
lock_guard
odercondition_variable
oder vielleichtcondition_variable::wait
?condition_variable_any
condition_variable::wait
. Ja,condition_variable_any
ist der richtige Weg. Und der Grund, warum Funktionalität nicht integriertcondition_variable
ist, ist, dass sie teurer ist. Und escondition_variable
ist 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 verwendencondition_variable_any
.condition_variable_any
mit einem einfachen Mutex missbrauchen könnte .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_lock
anstelle eines unformatiertenmutex
Benutzers 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).
quelle