我有两个用例。
a .我想同步两个线程对队列的访问。
B.我想同步访问两个线程的队列,并使用一个条件变量,因为其中一个线程将等待内容存储到队列由另一个线程。
对于用例A,我看到使用std::lock_guard<>的代码示例。对于用例B,我看到使用std::unique_lock<>的代码示例。
这两者之间的区别是什么?我应该在哪个用例中使用哪个?
我有两个用例。
a .我想同步两个线程对队列的访问。
B.我想同步访问两个线程的队列,并使用一个条件变量,因为其中一个线程将等待内容存储到队列由另一个线程。
对于用例A,我看到使用std::lock_guard<>的代码示例。对于用例B,我看到使用std::unique_lock<>的代码示例。
这两者之间的区别是什么?我应该在哪个用例中使用哪个?
当前回答
正如其他人所提到的,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&)只是针对unique_lock持有的互斥量调用底层pthread条件变量上的pthread_cond_wait()(因此也可以对lock_guard做同样的事情,但没有,因为标准没有提供这个功能) Std::condition_variable_any可以用于任何可锁对象,包括一个根本不是互斥锁的对象(因此它甚至可以用于进程间信号量)
其他回答
Lock_guard和unique_lock几乎是一样的东西;Lock_guard是一个受限版本,接口受限。
lock_guard始终持有一个锁,从构造到销毁。可以在不立即锁定的情况下创建unique_lock,可以在其存在的任何时候解锁,并且可以将锁的所有权从一个实例转移到另一个实例。
所以您总是使用lock_guard,除非您需要unique_lock的功能。一个条件变量需要一个unique_lock。
它们并不是相同的互斥对象,lock_guard<muType>与std::mutex几乎相同,不同之处在于它的生命周期在作用域的末尾结束(称为D-tor),因此关于这两个互斥对象的明确定义:
lock_guard<muType>具有在作用域块期间拥有互斥锁的机制。
And
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>带条件变量
一个缺失的区别是: Std::unique_lock可以移动,但Std::lock_guard不能移动。
注意:两者都不能复制。
不同之处在于您可以锁定和解锁std::unique_lock。Std::lock_guard只在建造时锁定一次,在破坏时解锁。
所以对于用例B,你肯定需要一个std::unique_lock作为条件变量。在A的情况下,这取决于你是否需要重新锁定警卫。
std::unique_lock有其他特性,允许它在不立即锁定互斥量的情况下被构造,而是构建RAII包装器(见这里)。
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::unique_lock替换std::lock_guard。但是,std::unique_lock可能会有更多的开销。
注意,现在(从c++ 17开始)应该使用std::scoped_lock而不是std::lock_guard。
正如其他人所提到的,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&)只是针对unique_lock持有的互斥量调用底层pthread条件变量上的pthread_cond_wait()(因此也可以对lock_guard做同样的事情,但没有,因为标准没有提供这个功能) Std::condition_variable_any可以用于任何可锁对象,包括一个根本不是互斥锁的对象(因此它甚至可以用于进程间信号量)