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


348

Ho due casi d'uso.

R. Voglio sincronizzare l'accesso di due thread in una coda.

B. Voglio sincronizzare l'accesso di due thread a una coda e utilizzare una variabile di condizione perché uno dei thread attenderà che il contenuto venga archiviato nella coda dall'altro thread.

Per il caso d'uso AI vedere l'esempio di codice usando std::lock_guard<>. Per il caso d'uso BI vedere l'esempio di codice usando std::unique_lock<>.

Qual è la differenza tra i due e quale dovrei usare in quale caso d'uso?

Risposte:


344

La differenza è che puoi bloccare e sbloccare a std::unique_lock. std::lock_guardsarà bloccato solo una volta durante la costruzione e sbloccato durante la distruzione.

Quindi, per il caso d'uso B, hai sicuramente bisogno di a std::unique_lockper la variabile condition. Nel caso A dipende se è necessario ricollegare la protezione.

std::unique_lockha altre caratteristiche che gli consentono di esempio: essere costruito senza bloccare immediatamente il mutex ma per costruire il wrapper RAII (vedi qui ).

std::lock_guardfornisce anche un comodo wrapper RAII, ma non può bloccare più mutex in modo sicuro. Può essere utilizzato quando è necessario un wrapper per un ambito limitato, ad esempio: una funzione membro:

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           
};

Chiarire una domanda per chmike, per impostazione predefinita std::lock_guarde std::unique_locksono gli stessi. Quindi, nel caso sopra, è possibile sostituire std::lock_guardcon std::unique_lock. Tuttavia, std::unique_lockpotrebbe avere un po 'più di spese generali.

Si noti che in questi giorni si dovrebbe usare std::scoped_lockal posto di std::lock_guard.


2
Con l'istruzione std :: unique_lock <std :: mutex> lock (myMutex); il mutex verrà bloccato dal costruttore?
Chmike,

3
@chmike Sì, lo farà. Aggiunti alcuni chiarimenti.
Stephan Dollberg,

10
@chmike Beh, penso che sia meno una questione di efficienza che di funzionalità. Se std::lock_guardè abbastanza per il tuo caso A, allora dovresti usarlo. Non solo evita inutili spese generali, ma mostra anche al lettore l'intenzione di non sbloccare mai questa protezione.
Stephan Dollberg,

5
@chmike: teoricamente sì. Tuttavia, le mutice non sono esattamente dei costrutti leggeri, quindi unique_lockè probabile che l'overhead aggiuntivo sia ridotto dal costo del blocco e dello sblocco effettivi del mutex (se il compilatore non ottimizzasse tale overhead, il che sarebbe possibile).
Grizzly,

6
So for usecase B you definitely need a std::unique_lock for the condition variable- sì, ma solo nel thread che cv.wait()è, perché quel metodo rilascia atomicamente il mutex. Nell'altro thread in cui aggiorni le variabili condivise e poi chiami cv.notify_one(), lock_guardbasta un semplice blocco del mutex nell'ambito ... a meno che tu non stia facendo qualcosa di più elaborato che non riesco a immaginare! ad es. en.cppreference.com/w/cpp/thread/condition_variable - funziona per me :)
underscore_d

115

lock_guarde unique_locksono praticamente la stessa cosa; lock_guardè una versione limitata con un'interfaccia limitata.

A lock_guardtiene sempre una serratura dalla sua costruzione alla sua distruzione. A unique_lockpuò essere creato senza blocco immediato, può essere sbloccato in qualsiasi momento della sua esistenza e può trasferire la proprietà del blocco da un'istanza all'altra.

Quindi usi sempre lock_guard , a meno che tu non abbia bisogno delle capacità di unique_lock. A ha condition_variablebisogno di a unique_lock.


11
A condition_variable needs a unique_lock.- sì, ma solo dal wait()lato ing, come elaborato nel mio commento all'inf.
underscore_d

48

Utilizzare a lock_guardmeno che non sia necessario essere in grado di eseguire manualmente unlockil mutex nel mezzo senza distruggere il file lock.

In particolare, condition_variablesblocca il suo mutex quando va a dormire dopo le chiamate wait. Ecco perché a lock_guardnon è sufficiente qui.


Passare un lock_guard a uno dei metodi di attesa della variabile condizionale andrebbe bene perché il mutex viene sempre riacquistato quando l'attesa termina, per qualsiasi motivo. Tuttavia, lo standard fornisce solo un'interfaccia per unique_lock. Questo potrebbe essere considerato un difetto dello standard.
Chris Vine,

3
@Chris In questo caso spezzeresti comunque l'incapsulamento. Il metodo wait dovrebbe essere in grado di estrarre il mutex da lock_guarde sbloccarlo, interrompendo così temporaneamente l'invariante di classe della guardia. Anche se questo accade invisibile all'utente, considererei questo un motivo legittimo per non consentire l'uso di lock_guardin questo caso.
ComicSansMS il

In tal caso, sarebbe invisibile e non rilevabile. gcc-4.8 lo fa. wait (unique_lock <mutex> &) chiama __gthread_cond_wait (& _ M_cond, __lock.mutex () -> native_handle ()) (vedi libstdc ++ - v3 / src / c ++ 11 / condition_variable.cc), che chiama pthread_cond_wait () (vedi libgcc /gthr-posix.h). Lo stesso potrebbe essere fatto per lock_guard (ma non è perché non è nello standard per condition_variable).
Chris Vine,

4
@Chris Il punto lock_guardnon consente affatto di recuperare il mutex sottostante. Questa è una limitazione deliberata per consentire un ragionamento più semplice sul codice che utilizza lock_guardal contrario del codice che utilizza un unique_lock. L'unico modo per ottenere ciò che chiedi è rompere deliberatamente l'incapsulamento della lock_guardclasse ed esporre la sua implementazione a una classe diversa (in questo caso il condition_variable). Questo è un prezzo difficile da pagare per il discutibile vantaggio dell'utente di una variabile di condizione che non deve ricordare la differenza tra i due tipi di blocco.
ComicSansMS

4
@ Chris Dove hai avuto l'idea che condition_variable_any.waitavrebbe funzionato con un lock_guard? Lo standard richiede il tipo di blocco fornito per soddisfare il BasicLockablerequisito (§30.5.2), che lock_guardnon lo fa. Lo fa solo il suo mutex sottostante, ma per motivi che ho sottolineato in precedenza l'interfaccia di lock_guardnon fornisce l'accesso al mutex.
ComicSansMS

11

Ci sono alcune cose comuni tra lock_guarde unique_locke alcune differenze.

Ma nel contesto della domanda posta, il compilatore non consente l'utilizzo di a lock_guard in combinazione con una variabile di condizione, perché quando un thread chiama aspetta una variabile di condizione, il mutex viene sbloccato automaticamente e quando altri thread / thread lo notificano e il thread corrente viene richiamato (esce dall'attesa), il blocco viene riacquistato.

Questo fenomeno è contrario al principio di lock_guard. lock_guardpuò essere costruito solo una volta e distrutto solo una volta.

Quindi lock_guardnon può essere usato in combinazione con una variabile di condizione, ma unique_lockpuò essere (perché unique_lockpuò essere bloccato e sbloccato più volte).


5
he compiler does not allow using a lock_guard in combination with a condition variableQuesto è falso Certamente non permette e lavorare perfettamente con una lock_guardsul notify()lato ing. Solo il wait()lato int richiede un unique_lock, perché wait()deve rilasciare il blocco durante il controllo della condizione.
underscore_d

0

Non sono davvero gli stessi mutex, lock_guard<muType>ha quasi lo stesso std::mutex, con la differenza che la sua durata termina alla fine dell'ambito (chiamato D-tor) quindi una chiara definizione di questi due mutex:

lock_guard<muType> ha un meccanismo per possedere un mutex per la durata di un blocco con ambito.

E

unique_lock<muType> è un wrapper che consente il blocco differito, i tentativi di blocco vincolati nel tempo, il blocco ricorsivo, il trasferimento della proprietà del blocco e l'uso con variabili di condizione.

Ecco un esempio di implementazione:

#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;
}

In questo esempio, ho usato il unique_lock<muType>concondition variable


-5

Come è stato detto da altri, std :: unique_lock tiene traccia dello stato bloccato del mutex, quindi è possibile posticipare il blocco fino a dopo la costruzione del blocco e sbloccare prima della distruzione del blocco. std :: lock_guard non lo consente.

Non sembra esserci alcun motivo per cui le funzioni di attesa std :: condition_variable non dovrebbero accettare un lock_guard e un unique_lock, perché ogni volta che un'attesa termina (per qualsiasi motivo) il mutex viene automaticamente riacquistato in modo da non causare alcuna violazione semantica. Tuttavia, secondo lo standard, per utilizzare uno std :: lock_guard con una variabile di condizione è necessario utilizzare uno std :: condition_variable_any anziché std :: condition_variable.

Modifica : eliminato "L'utilizzo dell'interfaccia pthreads std :: condition_variable e std :: condition_variable_any dovrebbe essere identico". Osservando l'implementazione di gcc:

  • std :: condition_variable :: wait (std :: unique_lock &) chiama solo pthread_cond_wait () sulla variabile di condizione pthread sottostante rispetto al mutex tenuto da unique_lock (e quindi potrebbe fare lo stesso per lock_guard, ma non lo fa perché lo standard non lo prevede)
  • std :: condition_variable_any può funzionare con qualsiasi oggetto bloccabile, incluso uno che non è affatto un blocco mutex (potrebbe quindi funzionare anche con un semaforo tra processi)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.