std :: lock_guard oder std :: scoped_lock?

142

In C ++ 17 wurde eine neue Sperrklasse namens eingeführt std::scoped_lock.

Aus der Dokumentation geht hervor, dass es der bereits vorhandenen std::lock_guardKlasse ähnelt .

Was ist der Unterschied und wann sollte ich es verwenden?

Stephan Dollberg
quelle

Antworten:

124

Dies scoped_lockist eine streng überlegene Version lock_guard, die eine beliebige Anzahl von Mutexen gleichzeitig sperrt (unter Verwendung des gleichen Deadlock-Vermeidungsalgorithmus wie std::lock). In neuem Code sollten Sie immer nur verwenden scoped_lock.

Der einzige Grund, der lock_guardnoch besteht, ist die Kompatibilität. Es konnte nicht einfach gelöscht werden, da es im aktuellen Code verwendet wird. Darüber hinaus erwies es sich als unerwünscht, seine Definition zu ändern (von unär zu variadisch), da dies auch eine beobachtbare und damit brechende Änderung ist (jedoch aus etwas technischen Gründen).

Kerrek SB
quelle
8
Dank der Argumentableitung für Klassenvorlagen müssen Sie nicht einmal die abschließbaren Typen auflisten.
Nicol Bolas
3
@NicolBolas: Das stimmt, aber das gilt auch für lock_guard. Aber es macht die Wachklassen sicherlich ein bisschen einfacher zu benutzen.
Kerrek SB
6
scoped_lock ist nur C ++ 17
Shital Shah
1
Da es sich um C ++ 17 handelt, ist die Kompatibilität ein besonders guter Grund für seine Existenz. Ich bin auch vehement anderer Meinung als die absolutistische Behauptung "Sie sollten immer nur verwenden", wenn die Tinte noch von diesem Standard abtrocknet.
Paul Childs
87

Der einzige und wichtige Unterschied besteht darin, dass std::scoped_lockein variadischer Konstruktor mehr als einen Mutex verwendet. Dies ermöglicht es, mehrere Mutexe in einem Deadlock zu sperren und so zu vermeiden, als ob std::locksie verwendet würden.

{
    // safely locked as if using std::lock
    std::scoped_lock<std::mutex, std::mutex> lock(mutex1, mutex2);     
}

Zuvor mussten Sie ein wenig tanzen, um mehrere Mutexe auf sichere Weise zu sperren, indem Sie diese Antwortstd::lock wie erläutert verwenden .

Das Hinzufügen einer Oszilloskopsperre erleichtert die Verwendung und vermeidet die damit verbundenen Fehler. Sie können als std::lock_guardveraltet betrachten. Der Einzelargumentfall vonstd::scoped_lock kann als Spezialisierung implementiert werden, sodass Sie sich nicht vor möglichen Leistungsproblemen fürchten müssen.

GCC 7 hat bereits Unterstützung, std::scoped_lockdie hier zu sehen ist .

Weitere Informationen finden Sie im Standardpapier

Stephan Dollberg
quelle
9
Beantwortete Ihre eigene Frage nach nur 10 min. Wussten Sie wirklich nicht?
Walter
23
@ Walter Ich habe stackoverflow.blog/2011/07/01/…
Stephan Dollberg
3
Als ich es im Ausschuss ansprach, war die Antwort "nichts". Es kann sein, dass der entartete Fall eines Algorithmus genau das Richtige ist. Oder es kann sein, dass genug Leute versehentlich nichts sperren, wenn sie beabsichtigen, etwas zu sperren, was ein häufiges Problem ist. Ich bin mir wirklich nicht sicher.
Howard Hinnant
3
@ HowardHinnant : scoped_lock lk; // locks all mutexes in scope. LGTM.
Kerrek SB
2
@ KerrekSB: scoped_lock lk;ist die neue Abkürzung für scoped_lock<> lk;. Es gibt keine Mutexe. Du hast also recht. ;-)
Howard Hinnant
25

Späte Antwort und meistens als Antwort auf:

Sie können als std::lock_guardveraltet betrachten.

Für den allgemeinen Fall, dass man genau einen Mutex sperren muss, std::lock_guardgibt es eine API, die etwas sicherer zu verwenden ist als scoped_lock.

Beispielsweise:

{
   std::scoped_lock lock; // protect this block
   ...
}

Das obige Snippet ist wahrscheinlich ein versehentlicher Laufzeitfehler, da es kompiliert und dann absolut nichts tut. Der Codierer meinte wahrscheinlich:

{
   std::scoped_lock lock{mut}; // protect this block
   ...
}

Jetzt wird es gesperrt / entsperrt mut.

Wenn lock_guardstattdessen in den beiden obigen Beispielen verwendet wurde, ist das erste Beispiel ein Kompilierungsfehler anstelle eines Laufzeitfehlers, und das zweite Beispiel hat die gleiche Funktionalität wie die verwendete Version scoped_lock.

Mein Rat ist also, das einfachste Werkzeug für den Job zu verwenden:

  1. lock_guard Wenn Sie genau 1 Mutex für einen gesamten Bereich sperren müssen.

  2. scoped_lock Wenn Sie eine Anzahl von Mutexen sperren müssen, ist dies nicht genau 1.

  3. unique_lockwenn Sie im Rahmen des Blocks entsperren müssen (einschließlich der Verwendung mit a condition_variable).

Dieser Ratschlag bedeutet nicht , dass er scoped_lockneu gestaltet werden sollte, um keine 0 Mutexe zu akzeptieren. Es gibt gültige Anwendungsfälle, in denen es wünschenswert ist scoped_lock, variable Vorlagenparameterpakete zu akzeptieren, die möglicherweise leer sind. Und der leere Koffer sollte nichts verriegeln.

Und deshalb lock_guardist es nicht veraltet. scoped_lock und unique_lock mag eine Obermenge der Funktionalität von sein lock_guard, aber diese Tatsache ist ein zweischneidiges Schwert. Manchmal ist es genauso wichtig, was ein Typ nicht kann (in diesem Fall Standardkonstrukt).

Howard Hinnant
quelle
13

Hier ist ein Beispiel und ein Zitat aus C ++ Concurrency in Action :

friend void swap(X& lhs, X& rhs)
{
    if (&lhs == & rhs)
        return;
    std::lock(lhs.m, rhs.m);
    std::lock_guard<std::mutex> lock_a(lhs.m, std::adopt_lock);
    std::lock_guard<std::mutex> lock_b(rhs.m, std::adopt_lock);
    swap(lhs.some_detail, rhs.some_detail);
}

vs.

friend void swap(X& lhs, X& rhs)
{
    if (&lhs == &rhs)
        return;
    std::scoped_lock guard(lhs.m, rhs.m);
    swap(lhs.some_detail, rhs.some_detail);
}

Das Vorhandensein von std::scoped_lockbedeutet, dass die meisten Fälle, die Sie std::lockvor c ++ 17 verwendet hätten, jetzt std::scoped_lockmit weniger Fehlerpotential geschrieben werden können, was nur eine gute Sache sein kann!

陳 力
quelle