std :: unique_lock <std :: mutex> або std :: lock_guard <std :: mutex>?


349

У мене є два випадки використання.

A. Я хочу синхронізувати доступ двома потоками до черги.

B. Я хочу синхронізувати доступ двома потоками до черги та використовувати змінну умови, оскільки одна з ниток буде чекати вмісту, який буде збережений у черзі іншим потоком.

Для випадку AI див. Приклад коду з використанням std::lock_guard<>. Для використання випадку BI див. Приклад коду з використанням std::unique_lock<>.

Яка різниця між цими двома та якими слід користуватися у разі використання?

Відповіді:


344

Різниця полягає в тому, що ви можете заблокувати та розблокувати a std::unique_lock. std::lock_guardбуде заблокований лише один раз на конструкції та розблокований при знищенні.

Тому для використання випадку B вам обов'язково потрібна std::unique_lockзмінна умова. У випадку A, це залежить від того, чи потрібно вимкнути охорону.

std::unique_lockмає і інші функції , які дозволяють йому , наприклад: бути побудований без негайного блокування мьютекса , але будувати RAII оболонку (див тут ).

std::lock_guardтакож забезпечує зручну оболонку RAII, але не може безпечно заблокувати кілька файлів. Його можна використовувати, коли вам потрібна обгортка для обмеженої області, наприклад: функція члена:

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

Для того, щоб прояснити питання по chmike, за замовчуванням std::lock_guardі std::unique_lockє однаковими. Тож у наведеному вище випадку ви можете замінити std::lock_guardна std::unique_lock. Однак, std::unique_lockможливо, більше, ніж накладні.

Зауважте, що в ці дні слід використовувати std::scoped_lockзамість цього std::lock_guard.


2
З інструкцією std :: unique_lock <std :: mutex> lock (myMutex); чи буде заблокована мютекс конструктором?
chmike

3
@chmike Так, так і буде. Додано трохи уточнень.
Стефан Доллберг

10
@chmike Ну, я думаю, що це менше питання ефективності, ніж функціональності. Якщо std::lock_guardвашої справи А достатньо, то вам слід скористатися нею. Це не тільки уникає зайвих накладних витрат, але й показує намір читача, що ви ніколи не розблокуєте цю охорону.
Стефан Доллберг

5
@chmike: Теоретично так. Однак Mutices - це не дуже легкі конструкції, тому додаткові накладні витрати unique_lock, ймовірно, будуть уникнути вартістю фактичного блокування та розблокування мютексу (якщо компілятор не оптимізував цей накладні витрати, що це можливо).
Грізлі

6
So for usecase B you definitely need a std::unique_lock for the condition variable- так, але тільки в потоці, який є cv.wait(), тому що цей метод атомарно звільняє мютекс. В іншому потоці, де ви оновлюєте загальну змінну (и), а потім дзвоните cv.notify_one(), lock_guardдостатньо простого, щоб зафіксувати мютекс в області дії ... якщо ви не робите нічого більш детального, що я не уявляю! наприклад, en.cppreference.com/w/cpp/thread/condition_variable - працює для мене :)
підкреслюй_d

115

lock_guardі unique_lockмайже однакове; lock_guardце обмежена версія з обмеженим інтерфейсом.

A lock_guardзавжди тримає замок від його будівництва до руйнування. А unique_lockможе бути створений без негайного блокування, може розблокуватись у будь-яку точку свого існування та може передати право власності на замок з одного примірника на інший.

Тому ви завжди користуєтесь lock_guard, якщо вам не потрібні можливості unique_lock. А condition_variableпотребує unique_lock.


11
A condition_variable needs a unique_lock.- так, але лише з wait()іншої сторони, як було розроблено в моєму коментарі до інф.
підкреслюйте_d

48

Використовуйте, lock_guardякщо вам не вдається вручну unlockввімкнути мютекс між ними, не руйнуючи його lock.

Зокрема, condition_variableрозблокує свою мутекс, коли йде спати при дзвінках до wait. Ось чому а lock_guardтут недостатньо.


Передача lock_guard до одного з методів очікування умовної змінної було б чудово, тому що mutex завжди знов набувається, коли очікування закінчується з будь-якої причини. Однак стандарт пропонує лише інтерфейс для unique_lock. Це може розцінюватися як недолік стандарту.
Кріс Вайн

3
@Chris У цьому випадку ви все одно порушите інкапсуляцію. Метод очікування повинен мати можливість витягувати мютекс із lock_guardта розблокувати його, тимчасово порушуючи інваріант класу охорони. Незважаючи на те, що це трапляється невидимим для користувача, я вважаю, що це законна причина заборони використання lock_guardв цьому випадку.
ComicSansMS

Якщо так, це було б непомітно і невизначно. gcc-4,8 робить це. зачекайте (унікальний_блок <mutex> &) викликає __gthread_cond_wait (& _ M_cond, __lock.mutex () -> native_handle ()) (див. libstdc ++ - v3 / src / c ++ 11 / condition_variable.cc), який викликає pthread_cond_wait () (див. libgcc /gthr-posix.h). Те саме можна зробити для lock_guard (але це не тому, що це не в стандарті для condition_variable).
Кріс Вайн

4
@Chris Суть у тому, що lock_guardвзагалі не дозволяє отримати базовий мютекс. Це навмисне обмеження, щоб дозволити простіші міркування про код, який використовується lock_guard, на відміну від коду, який використовує unique_lock. Єдиний спосіб досягти того, що ви просите, - це свідомо порушити інкапсуляцію lock_guardкласу і піддавати його реалізації іншому класу (в даному випадку - condition_variable). Це важка ціна, яку потрібно заплатити за сумнівну перевагу користувача змінної умови, що не повинен пам’ятати про різницю між двома типами блокування.
ComicSansMS

4
@Chris Звідки ти взяв ідею, що condition_variable_any.waitпрацюватиме з lock_guard? Стандарт вимагає, щоб наданий тип блокування відповідав BasicLockableвимозі (§30.5.2), що lock_guardне відповідає. Працює лише його основна мютекс, але з причин, які я вказував раніше, інтерфейс lock_guardне забезпечує доступ до файлу.
ComicSansMS

11

Існують певні загальні речі між lock_guardі unique_lockта певними відмінностями.

Але в контексті заданого питання компілятор не дозволяє використовувати lock_guardкомбінацію зі змінною умови, тому що, коли потік викликає зачекання на змінну умови, mutex автоматично розблокується та коли інші потоки / потоки сповіщають і поточний потік викликається (виходить з очікування), замок знову набувається.

Це явище суперечить принципу lock_guard.lock_guardможе бути побудований лише один раз і знищений лише один раз.

Отже, lock_guardне можна використовувати в поєднанні зі змінною умови, але це unique_lockможе бути (тому що unique_lockїї можна заблокувати та розблокувати кілька разів).


5
he compiler does not allow using a lock_guard in combination with a condition variableЦе помилково. Це , звичайно , це дозволяє і працювати відмінно з lock_guardна notify()ІНГ стороні. Тільки wait()внутрішня сторона вимагає символу "a" unique_lock, оскільки він wait()повинен звільнити замок, перевіряючи стан.
підкреслюй_d

0

Вони насправді не є однаковими мутексами, lock_guard<muType>мають майже те саме, що std::mutex, з тією різницею, що термін експлуатації закінчується в кінці області (D-tor називається), тому чітке визначення щодо цих двох мутексів:

lock_guard<muType> має механізм володіння мютексом протягом тривалості блоку, що охоплюється.

І

unique_lock<muType> це обгортка, що дозволяє відкладати блокування, обмежені часом спроби блокування, рекурсивне блокування, передачу права власності на замок та використання змінних умов.

Ось приклад реалізації:

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

У цьому прикладі я використав unique_lock<muType>сcondition variable


-5

Як вже згадували інші, std :: unique_lock відслідковує стан заблокованого мютексу, тож ви можете відкласти блокування до моменту побудови блокування та розблокувати перед знищенням замка. std :: lock_guard цього не дозволяє.

Здається, немає причин, через які функції очікування std :: condition_variable не повинні приймати lock_guard, а також унікальний_lock, тому що щоразу, коли очікування закінчується (з будь-якої причини), мютекс автоматично заново набувається, щоб не викликати семантичних порушень. Однак відповідно до стандарту, щоб використовувати std :: lock_guard зі змінною умови, ви повинні використовувати std :: condition_variable_any замість std :: condition_variable.

Редагувати : видалено "Використання інтерфейсу pthreads std :: condition_variable та std :: condition_variable_any має бути однаковим". Перегляд реалізації gcc:

  • std :: condition_variable :: wait (std :: unique_lock &) просто викликає pthread_cond_wait () на нижню змінну умови pthread стосовно mutex, що міститься у файлі unique_lock (і так би можна було однаково зробити те саме для lock_guard, але не тому, що стандарт не передбачає цього)
  • std :: condition_variable_any може працювати з будь-яким об'єктом, що блокується, включаючи той, який взагалі не є блокуванням mutex (тому він може працювати навіть із міжпроцесорним семафором)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.