std::unique_lock<std:: mutex> или std:: lock_guard<std:: mutex>?

У меня есть два варианта использования.

О. Я хочу синхронизировать доступ двумя потоками к очереди.

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

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

В чем разница между этими двумя и какой я должен использовать в каком случае использования?

5 ответов

Решение

Разница в том, что вы можете заблокировать и разблокировать std::unique_lock, std::lock_guard будет заблокирован только один раз на строительстве и разблокирован при уничтожении.

Так что для варианта использования B вам определенно нужен std::unique_lock для условной переменной. В случае А это зависит от того, нужно ли вам снова заблокировать охрану.

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,

lock_guard а также unique_lock в значительной степени одно и то же; lock_guard является ограниченной версией с ограниченным интерфейсом.

lock_guard всегда держит замок от его строительства до его разрушения. unique_lock может быть создан без немедленной блокировки, может разблокироваться в любой момент своего существования и может передавать право собственности на блокировку из одного экземпляра в другой.

Так что вы всегда используете lock_guard, если вам не нужны возможности unique_lock, condition_variable нужен unique_lock,

Использование lock_guard если вам не нужно иметь возможность вручную unlock мьютекс между ними, не разрушая lock,

Особенно, condition_variable открывает свой мьютекс, когда ложится спать на звонки wait, Вот почему lock_guard здесь недостаточно

Есть определенные общие вещи между lock_guard а также unique_lock и определенные различия.

Но в контексте поставленного вопроса компилятор не позволяет использовать lock_guard в сочетании с условной переменной, потому что, когда поток вызывает ожидание для условной переменной, мьютекс автоматически разблокируется, и когда другие потоки / потоки уведомляют об этом, и текущий поток вызывается (выходит из режима ожидания), блокировка повторно устанавливается.

Это явление противоречит принципу lock_guard, lock_guard может быть построен только один раз и разрушен только один раз.

следовательно lock_guard не может использоваться в сочетании с условной переменной, но unique_lock может быть (потому что unique_lock может быть заблокирован и разблокирован несколько раз).

Одно недостающее отличие:std::unique_lock можно переместить, но std::lock_guard нельзя переместить.

Примечание: оба не могут быть скопированы.

На самом деле это не одни и те же мьютексы, 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

Как было упомянуто другими, std::unique_lock отслеживает заблокированное состояние мьютекса, поэтому вы можете отложить блокировку до завершения создания блокировки и разблокировать до разрушения блокировки. std::lock_guard не разрешает это.

Кажется, нет причины, по которой функции ожидания std:: condition_variable не должны принимать lock_guard, а также unique_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 по отношению к мьютексу, содержащемуся в unique_lock (и поэтому может в равной степени делать то же самое для lock_guard, но не потому, что стандарт не предусматривает этого)
  • std:: condition_variable_any может работать с любым блокируемым объектом, включая тот, который вообще не является блокировкой мьютекса (следовательно, он может работать даже с межпроцессным семафором)
Другие вопросы по тегам