std :: lock_guard czy std :: scoped_lock?

143

C ++ 17 wprowadził nową klasę blokady o nazwie std::scoped_lock.

Sądząc po dokumentacji, wygląda podobnie do już istniejącej std::lock_guardklasy.

Jaka jest różnica i kiedy powinienem go używać?

Stephan Dollberg
źródło

Odpowiedzi:

125

scoped_lockJest ściśle lepsze wersja lock_guard, która blokuje dowolną liczbę muteksy wszystkie na raz (przy użyciu tego samego algorytmu zakleszczenia unikania co std::lock). W nowym kodzie powinieneś używać tylko scoped_lock.

Jedynym powodem lock_guardwciąż istnieje jest kompatybilność. Nie można go było po prostu usunąć, ponieważ jest używany w obecnym kodzie. Co więcej, zmiana jego definicji (z jednoargumentowej na wariadyczną) okazała się niepożądana, ponieważ jest to również zmiana obserwowalna, a więc przełomowa (ale z powodów technicznych).

Kerrek SB
źródło
8
Ponadto, dzięki dedukcji argumentów z szablonu klasy, nie musisz nawet wymieniać typów, które można zablokować.
Nicol Bolas,
3
@NicolBolas: To prawda, ale dotyczy to również lock_guard. Ale z pewnością sprawia, że ​​klasy strażników są nieco łatwiejsze w użyciu.
Kerrek SB
6
scoped_lock to tylko C ++ 17
Shital Shah
1
Ponieważ jest to c ++ 17, kompatybilność jest szczególnie dobrym powodem jej istnienia. Zdecydowanie nie zgadzam się również z jakimkolwiek absolutystycznym twierdzeniem, że „należy używać tylko wtedy, gdy atrament nadal wysycha z tego standardu.
Paul Childs
88

Jedyną i ważną różnicą jest to, że std::scoped_lockkonstruktor wariadyczny przyjmuje więcej niż jeden muteks. Pozwala to na zablokowanie wielu muteksów w zakleszczeniu, unikając w ten sposób, jakby std::lockbyły używane.

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

Wcześniej trzeba było wykonać mały taniec, aby zablokować wiele muteksów w bezpieczny sposób, używając, std::lockjak wyjaśniono w tej odpowiedzi .

Dodanie blokady zakresu ułatwia korzystanie z niego i pozwala uniknąć powiązanych błędów. Możesz uznać za std::lock_guardprzestarzałe. Pojedynczy przypadek argumentu std::scoped_lockmoże zostać zaimplementowany jako specjalizacja i nie musisz się obawiać o ewentualne problemy z wydajnością.

GCC 7 ma już wsparcie, std::scoped_lockktóre można zobaczyć tutaj .

Aby uzyskać więcej informacji, możesz przeczytać standardowy artykuł

Stephan Dollberg
źródło
9
Odpowiedział na Twoje pytanie już po 10 minutach. Czy naprawdę nie wiedziałeś?
Walter
23
@Walter I did stackoverflow.blog/2011/07/01/…
Stephan Dollberg
3
Kiedy przedstawiłem to w komisji, odpowiedź brzmiała „nic”. Może się zdarzyć, że zdegenerowany przypadek jakiegoś algorytmu, to jest dokładnie to, co trzeba. Może się też zdarzyć, że wielu ludzi przypadkowo niczego nie blokuje, gdy zamierzali coś zablokować, jest częstym problemem. Naprawdę nie jestem pewien.
Howard Hinnant,
3
@HowardHinnant: scoped_lock lk; // locks all mutexes in scope. LGTM.
Kerrek SB
2
@KerrekSB: scoped_lock lk;to nowy skrót dla scoped_lock<> lk;. Nie żadne muteksy. Więc masz rację. ;-)
Howard Hinnant
26

Późna odpowiedź, głównie w odpowiedzi na:

Możesz uznać za std::lock_guardprzestarzałe.

W typowym przypadku, gdy trzeba zablokować dokładnie jeden muteks, std::lock_guardma API, które jest trochę bezpieczniejsze w użyciu niż scoped_lock.

Na przykład:

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

Powyższy fragment kodu jest prawdopodobnie przypadkowym błędem w czasie wykonywania, ponieważ kompiluje się, a następnie nie robi absolutnie nic. Koder prawdopodobnie miał na myśli:

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

Teraz blokuje / odblokowuje mut.

Jeśli lock_guardzamiast tego został użyty w dwóch powyższych przykładach, pierwszy przykład jest błędem czasu kompilacji zamiast błędu czasu wykonywania, a drugi przykład ma identyczną funkcjonalność jak wersja, która używa scoped_lock.

Więc radzę użyć najprostszego narzędzia do pracy:

  1. lock_guard jeśli chcesz zablokować dokładnie 1 muteks dla całego zakresu.

  2. scoped_lock jeśli chcesz zablokować liczbę muteksów, która nie jest dokładnie 1.

  3. unique_lockjeśli potrzebujesz odblokować w zakresie bloku (co obejmuje użycie z a condition_variable).

Ta rada ma nie oznacza, że scoped_lockpowinna zostać zmieniona, aby nie akceptować 0 muteksy. Istnieją ważne przypadki użycia, w których pożądane scoped_lockjest akceptowanie pakietów parametrów szablonów z różnymi szablonami, które mogą być puste. A pusta obudowa nie powinna niczego blokować.

I dlatego lock_guardnie jest przestarzały. scoped_lock i unique_lock może być nadzbiorem funkcjonalności lock_guard, ale fakt ten jest mieczem obosiecznym. Czasami jest równie ważne, czego typ nie będzie robił (w tym przypadku konstrukcja domyślna).

Howard Hinnant
źródło
13

Oto przykład i cytat z 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);
}

Istnienie std::scoped_lockoznacza, że ​​większość przypadków, w których używałbyś std::lockprzed c ++ 17, można teraz pisać przy użyciu std::scoped_lock, z mniejszym potencjałem błędów, co może być tylko dobrą rzeczą!

陳 力
źródło