std :: unique_lock <std :: mutex> atau std :: lock_guard <std :: mutex>?

348

Saya punya dua kasus penggunaan.

A. Saya ingin menyinkronkan akses oleh dua utas ke antrian.

B. Saya ingin menyinkronkan akses oleh dua utas ke antrian dan menggunakan variabel kondisi karena salah satu utas akan menunggu konten untuk disimpan ke dalam antrian oleh utas lainnya.

Untuk kasus penggunaan, lihat contoh kode menggunakan std::lock_guard<>. Untuk kasus penggunaan BI lihat contoh kode menggunakan std::unique_lock<>.

Apa perbedaan antara keduanya dan yang mana yang harus saya gunakan dalam kasus penggunaan yang mana?

chmike
sumber

Jawaban:

344

Perbedaannya adalah bahwa Anda dapat mengunci dan membuka kunci a std::unique_lock. std::lock_guardakan dikunci hanya sekali pada konstruksi dan tidak terkunci pada kehancuran.

Jadi untuk kasus penggunaan B Anda pasti membutuhkan std::unique_lockvariabel kondisi. Dalam kasus A itu tergantung apakah Anda perlu mengunci kembali penjaga.

std::unique_lockmemiliki fitur lain yang memungkinkannya misalnya: dibangun tanpa mengunci mutex segera tetapi untuk membangun pembungkus RAII (lihat di sini ).

std::lock_guardjuga menyediakan pembungkus RAII yang nyaman, tetapi tidak dapat mengunci beberapa mutex dengan aman. Ini dapat digunakan ketika Anda membutuhkan pembungkus untuk lingkup terbatas, misalnya: fungsi anggota:

class MyClass{
    std::mutex my_mutex;
    void member_foo() {
        std::lock_guard<mutex_type> lock(this->my_mutex);            
        /*
         block of code which needs mutual exclusion (e.g. open the same 
         file in multiple threads).
        */

        //mutex is automatically released when lock goes out of scope           
};

Untuk mengklarifikasi pertanyaan dengan chmike, secara default std::lock_guarddan std::unique_locksama. Jadi dalam kasus di atas, Anda bisa menggantinya std::lock_guarddengan std::unique_lock. Namun, std::unique_lockmungkin sedikit lebih mahal.

Perhatikan bahwa hari ini seseorang harus menggunakan std::scoped_locksebagai ganti std::lock_guard.

Stephan Dollberg
sumber
2
Dengan instruksi std :: unique_lock <std :: mutex> lock (myMutex); akankah mutex dikunci oleh konstruktor?
chmike
3
@ chmike Ya, pasti. Menambahkan beberapa klarifikasi.
Stephan Dollberg
10
@ chmike Yah, saya pikir ini bukan masalah efisiensi daripada fungsionalitas. Jika std::lock_guardcukup untuk kasus A Anda, maka Anda harus menggunakannya. Tidak hanya menghindari overhead yang tidak perlu tetapi juga menunjukkan niat kepada pembaca bahwa Anda tidak akan pernah membuka kunci penjaga ini.
Stephan Dollberg
5
@ chmike: Secara teoritis ya. Namun Mutices bukan konstruksi yang ringan, sehingga overhead tambahan unique_lockkemungkinan akan dikerdilkan oleh biaya untuk benar-benar mengunci dan membuka kunci mutex (jika kompiler tidak mengoptimalkan overhead yang jauh, yang mungkin dilakukan).
Grizzly
6
So for usecase B you definitely need a std::unique_lock for the condition variable- ya tetapi hanya di utas yang cv.wait()s, karena metode itu secara atom melepaskan mutex. Di utas lain tempat Anda memperbarui variabel yang dibagi-pakai (s) dan kemudian menelepon cv.notify_one(), lock_guardcukup sederhana untuk mengunci mutex dalam-lingkup ... kecuali Anda melakukan sesuatu yang lebih rumit yang tidak dapat saya bayangkan! mis. en.cppreference.com/w/cpp/thread/condition_variable - berfungsi untuk saya :)
underscore_d
114

lock_guarddan hal unique_lockyang hampir sama; lock_guardadalah versi terbatas dengan antarmuka terbatas.

A lock_guardselalu memegang kunci dari konstruksi hingga kehancurannya. A unique_lockdapat dibuat tanpa segera mengunci, dapat membuka pada titik mana pun dalam keberadaannya, dan dapat mentransfer kepemilikan kunci dari satu contoh ke yang lain.

Jadi Anda selalu menggunakan lock_guard, kecuali jika Anda membutuhkan kemampuan unique_lock. SEBUAHcondition_variable membutuhkan unique_lock.

Sebastian Redl
sumber
11
A condition_variable needs a unique_lock.- ya tetapi hanya di wait()sisi ing, seperti yang dijelaskan dalam komentar saya ke inf.
underscore_d
48

Gunakan lock_guardkecuali Anda harus dapat secara manual unlockmenggunakan mutex di antaranya tanpa merusaknya lock.

Secara khusus, condition_variablemembuka kunci mutunya ketika akan tidur saat panggilan ke wait. Itu sebabnya a lock_guardtidak memadai di sini.

KomikSMS
sumber
Melewati lock_guard ke salah satu metode menunggu variabel kondisional akan baik-baik saja karena mutex selalu diperoleh kembali ketika menunggu berakhir, untuk alasan apa pun. Namun standar hanya menyediakan antarmuka untuk unique_lock. Ini bisa dianggap sebagai kekurangan dalam standar.
Chris Vine
3
@ Chris Anda masih akan memecahkan enkapsulasi dalam kasus ini. Metode menunggu harus dapat mengekstraksi mutex dari lock_guarddan membuka kuncinya, sehingga melanggar sementara kelas invarian penjaga. Meskipun ini terjadi tidak terlihat oleh pengguna, saya akan mempertimbangkan bahwa alasan yang sah untuk tidak mengizinkan penggunaan lock_guarddalam kasus ini.
ComicSansMS
Jika demikian, itu tidak akan terlihat dan tidak terdeteksi. gcc-4.8 melakukannya. tunggu (unique_lock <mutex> &) panggilan __gthread_cond_wait (& _ M_cond, __lock.mutex () -> native_handle ()) (lihat libstdc ++ - v3 / src / c ++ 11 / condition_variable.cc), yang memanggil pthread_cond_wait () (lihat libg) (lihat libg) /gthr-posix.h). Hal yang sama dapat dilakukan untuk lock_guard (tetapi bukan karena itu tidak dalam standar untuk condition_variable).
Chris Vine
4
@ Chris Intinya adalah lock_guardtidak memungkinkan mengambil mutex yang mendasarinya sama sekali. Ini adalah batasan yang disengaja untuk memungkinkan penalaran yang lebih sederhana tentang kode yang digunakan lock_guardsebagai kebalikan dari kode yang menggunakan a unique_lock. Satu-satunya cara untuk mencapai apa yang Anda tanyakan adalah dengan secara sengaja memecah enkapsulasi lock_guardkelas dan mengekspos implementasinya ke kelas yang berbeda (dalam hal ini condition_variable). Ini adalah harga yang sulit untuk dibayar untuk keuntungan yang dipertanyakan dari pengguna variabel kondisi tidak harus mengingat perbedaan antara kedua jenis kunci.
ComicSansMS
4
@ Chris Di mana Anda mendapatkan ide yang condition_variable_any.waitakan berhasil dengan lock_guard? Standar ini mengharuskan jenis Kunci yang disediakan untuk memenuhi BasicLockablepersyaratan (§30.5.2), yang lock_guardtidak. Hanya mutex yang mendasarinya, tetapi karena alasan yang saya tunjukkan sebelumnya antarmuka lock_guardtidak menyediakan akses ke mutex.
ComicSansMS
11

Ada beberapa hal umum antara lock_guarddan unique_lockdan perbedaan tertentu.

Tetapi dalam konteks pertanyaan yang diajukan, kompiler tidak mengizinkan penggunaan a lock_guard kombinasi dengan variabel kondisi, karena ketika sebuah thread panggilan menunggu variabel kondisi, mutex akan dibuka secara otomatis dan ketika thread / utas lainnya memberi tahu dan utas saat ini dipanggil (keluar dari menunggu), kunci diperoleh kembali.

Fenomena ini bertentangan dengan prinsip lock_guard. lock_guarddapat dibangun hanya sekali dan dihancurkan hanya sekali.

Karenanya lock_guardtidak dapat digunakan dalam kombinasi dengan variabel kondisi, tetapi a unique_lockdapat (karena unique_lockdapat dikunci dan dibuka beberapa kali).

Sandeep
sumber
5
he compiler does not allow using a lock_guard in combination with a condition variableIni salah. Ini tentu saja memungkinkan dan bekerja dengan sempurna dengan lock_guarddukungan notify(). Hanya wait()sisi int memerlukan a unique_lock, karena wait()harus melepaskan kunci saat memeriksa kondisi.
underscore_d
0

Mereka bukan mutex yang benar-benar sama, lock_guard<muType>memiliki hampir sama dengan std::mutex, dengan perbedaan bahwa itu seumur hidup berakhir pada akhir lingkup (D-tor disebut) sehingga definisi yang jelas tentang dua mutex ini:

lock_guard<muType> memiliki mekanisme untuk memiliki mutex untuk durasi blok yang dicakup.

Dan

unique_lock<muType> adalah pembungkus yang memungkinkan penguncian yang tertunda, upaya penguncian terbatas waktu, penguncian rekursif, transfer kepemilikan kunci, dan digunakan dengan variabel kondisi.

Berikut ini contoh penerapannya:

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <chrono>

using namespace std::chrono;

class Product{

   public:

       Product(int data):mdata(data){
       }

       virtual~Product(){
       }

       bool isReady(){
       return flag;
       }

       void showData(){

        std::cout<<mdata<<std::endl;
       }

       void read(){

         std::this_thread::sleep_for(milliseconds(2000));

         std::lock_guard<std::mutex> guard(mmutex);

         flag = true;

         std::cout<<"Data is ready"<<std::endl;

         cvar.notify_one();

       }

       void task(){

       std::unique_lock<std::mutex> lock(mmutex);

       cvar.wait(lock, [&, this]() mutable throw() -> bool{ return this->isReady(); });

       mdata+=1;

       }

   protected:

    std::condition_variable cvar;
    std::mutex mmutex;
    int mdata;
    bool flag = false;

};

int main(){

     int a = 0;
     Product product(a);

     std::thread reading(product.read, &product);
     std::thread setting(product.task, &product);

     reading.join();
     setting.join();


     product.showData();
    return 0;
}

Dalam contoh ini, saya menggunakan unique_lock<muType>dengancondition variable

rekkalmd
sumber
-5

Seperti yang telah disebutkan oleh orang lain, std :: unique_lock melacak status terkunci dari mutex, sehingga Anda dapat menunda penguncian sampai setelah pembangunan kunci, dan membuka sebelum kerusakan kunci. std :: lock_guard tidak mengizinkan ini.

Tampaknya tidak ada alasan mengapa fungsi menunggu std :: condition_variable tidak boleh mengambil lock_guard serta unique_lock, karena setiap kali menunggu berakhir (untuk alasan apa pun) mutex secara otomatis diperoleh kembali sehingga tidak akan menyebabkan pelanggaran semantik. Namun menurut standar, untuk menggunakan std :: lock_guard dengan variabel kondisi, Anda harus menggunakan std :: condition_variable_any alih-alih std :: condition_variable.

Sunting : dihapus "Menggunakan antarmuka pthreads std :: condition_variable dan std :: condition_variable_any harus identik". Saat melihat implementasi gcc:

  • std :: condition_variable :: wait (std :: unique_lock &) hanya memanggil pthread_cond_wait () pada variabel kondisi pthread yang mendasari sehubungan dengan mutex yang dipegang oleh unique_lock (dan dengan demikian bisa sama-sama melakukan hal yang sama untuk lock_guard, tetapi tidak karena standar tidak menyediakan untuk itu)
  • std :: condition_variable_any dapat bekerja dengan objek yang dapat dikunci, termasuk yang bukan kunci mutex sama sekali (karena itu bahkan dapat bekerja dengan semafor antar-proses)
Chris Vine
sumber