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 может работать с любым блокируемым объектом, включая тот, который вообще не является блокировкой мьютекса (следовательно, он может работать даже с межпроцессным семафором)