std :: lock_guard atau std :: scoped_lock?

157

C ++ 17 memperkenalkan kelas kunci baru yang disebut std::scoped_lock.

Dilihat dari dokumentasinya sepertinya kelas yang sudah ada std::lock_guard.

Apa bedanya dan kapan saya harus menggunakannya?

Stephan Dollberg
sumber

Jawaban:

134

Ini scoped_lockadalah versi yang sangat unggul lock_guardyang mengunci sejumlah mutex sekaligus (menggunakan algoritme penghindaran kebuntuan yang sama seperti std::lock). Dalam kode baru, Anda hanya boleh menggunakan scoped_lock.

Satu-satunya alasan lock_guardmasih ada adalah untuk kompatibilitas. Itu tidak bisa dihapus begitu saja, karena digunakan dalam kode saat ini. Selain itu, terbukti tidak diinginkan untuk mengubah definisinya (dari unary ke variadic), karena itu juga dapat diamati, dan karenanya melanggar, perubahan (tetapi untuk alasan yang agak teknis).

Kerrek SB
sumber
9
Selain itu, berkat pengurangan argumen template kelas, Anda bahkan tidak perlu mencantumkan jenis yang dapat dikunci.
Nicol Bolas
3
@NicolBolas: Itu benar, tapi itu juga berlaku untuk lock_guard. Tapi tentu saja membuat kelas penjaga sedikit lebih mudah digunakan.
Kerrek SB
8
scoped_lock hanya untuk C ++ 17
Shital Shah
3
Karena ini adalah c ++ 17, kompatibilitas adalah alasan yang sangat bagus untuk keberadaannya. Saya juga sangat tidak setuju dengan klaim absolut "Anda hanya boleh menggunakan" ketika tinta masih mengering dari standar ini.
Paul Childs
96

Perbedaan tunggal dan penting adalah bahwa std::scoped_lockkonstruktor variadic mengambil lebih dari satu mutex. Hal ini memungkinkan untuk mengunci beberapa mutex dengan cara menghindari kebuntuan seolah-olah std::lockdigunakan.

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

Sebelumnya Anda harus melakukan sedikit tarian untuk mengunci beberapa mutex dengan cara yang aman menggunakan std::lockseperti yang dijelaskan jawaban ini .

Penambahan kunci ruang lingkup membuat ini lebih mudah digunakan dan menghindari kesalahan terkait. Anda dapat menganggap std::lock_guardtidak berlaku lagi. Kasus argumen tunggal std::scoped_lockdapat diimplementasikan sebagai spesialisasi dan Anda tidak perlu takut tentang kemungkinan masalah kinerja.

GCC 7 sudah memiliki dukungan std::scoped_lockyang bisa dilihat di sini .

Untuk informasi lebih lanjut Anda mungkin ingin membaca kertas standar

Stephan Dollberg
sumber
9
Menjawab pertanyaan Anda sendiri setelah hanya 10 menit. Apa kamu benar-benar tidak tahu?
Walter
26
@Walter Saya melakukan stackoverflow.blog/2011/07/01/…
Stephan Dollberg
3
Ketika saya membahasnya di komite, jawabannya adalah "tidak ada". Mungkin kasus kemunduran dari beberapa algoritma, ini adalah hal yang benar. Atau mungkin cukup banyak orang yang secara tidak sengaja mengunci apa pun ketika mereka bermaksud mengunci sesuatu adalah masalah umum. Saya benar-benar tidak yakin.
Howard Hinnant
3
@HowardHinnant: scoped_lock lk; // locks all mutexes in scope. LGTM.
Kerrek SB
2
@KerrekSB: scoped_lock lk;adalah singkatan baru untuk scoped_lock<> lk;. Ada yang tidak ada mutexes. Jadi kamu benar. ;-)
Howard Hinnant
41

Jawaban terlambat, dan sebagian besar menanggapi:

Anda dapat menganggap std::lock_guardtidak berlaku lagi.

Untuk kasus umum di mana seseorang perlu mengunci tepat satu mutex, std::lock_guardmemiliki API yang sedikit lebih aman untuk digunakan daripada scoped_lock.

Sebagai contoh:

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

Cuplikan di atas kemungkinan besar merupakan kesalahan waktu proses yang tidak disengaja karena ia mengkompilasi dan kemudian tidak melakukan apa pun. Pembuat kode itu mungkin berarti:

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

Sekarang mengunci / membuka kunci mut.

Jika lock_guarddigunakan dalam dua contoh di atas, contoh pertama adalah kesalahan waktu kompilasi dan bukan kesalahan waktu proses, dan contoh kedua memiliki fungsionalitas yang identik dengan versi yang digunakan scoped_lock.

Jadi saran saya adalah menggunakan alat paling sederhana untuk pekerjaan itu:

  1. lock_guard jika Anda perlu mengunci tepat 1 mutex untuk seluruh cakupan.

  2. scoped_lock jika Anda perlu mengunci sejumlah mutex yang tidak persis 1.

  3. unique_lockjika Anda perlu membuka kunci dalam cakupan blok (yang mencakup penggunaan dengan a condition_variable).

Saran ini tidak menyiratkan bahwa scoped_lockharus didesain ulang untuk tidak menerima 0 mutex. Terdapat kasus penggunaan yang valid di mana diinginkan untuk scoped_lockmenerima paket parameter template variadic yang mungkin kosong. Dan kotak kosong seharusnya tidak mengunci apapun.

Dan itulah mengapa lock_guardtidak ditinggalkan. scoped_lock dan unique_lock mungkin merupakan superset dari fungsionalitas lock_guard, tetapi fakta itu adalah pedang bermata dua. Terkadang sama pentingnya dengan apa yang tidak akan dilakukan oleh suatu tipe (konstruksi default dalam kasus ini).

Howard Hinnant
sumber
14

Berikut adalah contoh dan kutipan dari 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);
}

Adanya std::scoped_locksarana bahwa sebagian besar kasus di mana Anda akan menggunakan std::locksebelum c ++ 17 sekarang dapat ditulis menggunakan std::scoped_lock, dengan potensi kesalahan yang lebih kecil, yang hanya bisa menjadi hal yang baik!

陳 力
sumber