[英]std::unique_lock<std::mutex> or std::lock_guard<std::mutex>?
我有两个用例。
A. 我想同步两个线程对一个队列的访问。
B. 我想同步两个线程对队列的访问并使用条件变量,因为其中一个线程将等待另一个线程将内容存储到队列中。
对于用例 AI,请参阅使用std::lock_guard<>
代码示例。 对于用例 BI,请参阅使用std::unique_lock<>
代码示例。
两者之间有什么区别,我应该在哪个用例中使用哪个?
不同之处在于您可以锁定和解锁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
可能会有更多的开销。
请注意,现在(从 C++17 开始)应该使用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
情况下手动unlock
互斥锁,否则请使用lock_guard
。
特别是, condition_variable
在调用wait
后进入睡眠状态时会解锁其互斥锁。 这就是为什么lock_guard
在这里是不够的。
如果您已经使用 C++17 或更高版本,请考虑使用scoped_lock
作为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 的实现时:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.