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_guard
klasy.
Jaka jest różnica i kiedy powinienem go używać?
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_guard
klasy.
Jaka jest różnica i kiedy powinienem go używać?
Odpowiedzi:
scoped_lock
Jest ś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_guard
wciąż 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).
lock_guard
. Ale z pewnością sprawia, że klasy strażników są nieco łatwiejsze w użyciu.
Jedyną i ważną różnicą jest to, że std::scoped_lock
konstruktor wariadyczny przyjmuje więcej niż jeden muteks. Pozwala to na zablokowanie wielu muteksów w zakleszczeniu, unikając w ten sposób, jakby std::lock
był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::lock
jak 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_guard
przestarzałe. Pojedynczy przypadek argumentu std::scoped_lock
może zostać zaimplementowany jako specjalizacja i nie musisz się obawiać o ewentualne problemy z wydajnością.
GCC 7 ma już wsparcie, std::scoped_lock
które można zobaczyć tutaj .
Aby uzyskać więcej informacji, możesz przeczytać standardowy artykuł
scoped_lock lk; // locks all mutexes in scope
. LGTM.
scoped_lock lk;
to nowy skrót dla scoped_lock<> lk;
. Nie są żadne muteksy. Więc masz rację. ;-)
Późna odpowiedź, głównie w odpowiedzi na:
Możesz uznać za
std::lock_guard
przestarzałe.
W typowym przypadku, gdy trzeba zablokować dokładnie jeden muteks, std::lock_guard
ma 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_guard
zamiast 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:
lock_guard
jeśli chcesz zablokować dokładnie 1 muteks dla całego zakresu.
scoped_lock
jeśli chcesz zablokować liczbę muteksów, która nie jest dokładnie 1.
unique_lock
jeśli potrzebujesz odblokować w zakresie bloku (co obejmuje użycie z a condition_variable
).
Ta rada ma nie oznacza, że scoped_lock
powinna zostać zmieniona, aby nie akceptować 0 muteksy. Istnieją ważne przypadki użycia, w których pożądane scoped_lock
jest 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_guard
nie 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).
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_lock
oznacza, że większość przypadków, w których używałbyśstd::lock
przed c ++ 17, można teraz pisać przy użyciustd::scoped_lock
, z mniejszym potencjałem błędów, co może być tylko dobrą rzeczą!