我有两个用例。
A. 我想同步访问两个线程的队列。
B. 我想同步两个线程对队列的访问并使用条件变量,因为其中一个线程将等待另一个线程将内容存储到队列中。
对于用例 AI,请参阅使用std::lock_guard<>
. 对于用例 BI,请参阅使用std::unique_lock<>
.
两者之间有什么区别,我应该在哪个用例中使用哪一个?
我有两个用例。
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
是具有受限界面的受限版本。
Alock_guard
从它的构建到它的破坏总是持有一把锁。Aunique_lock
可以在不立即锁定的情况下创建,可以在其存在的任何时候解锁,并且可以将锁的所有权从一个实例转移到另一个实例。
所以你总是使用lock_guard
,除非你需要unique_lock
. Acondition_variable
需要一个unique_lock
.
lock_guard
除非您需要能够在unlock
不破坏lock
.
特别是,condition_variable
在调用wait
. 这就是为什么 alock_guard
在这里是不够的。
如果您已经在使用 C++17 或更高版本,请考虑将其用作具有相同基本功能scoped_lock
的稍微改进的版本。lock_guard
之间有一些共同点lock_guard
,也有unique_lock
一些区别。
但是在所问问题的上下文中,编译器不允许将 alock_guard
与条件变量结合使用,因为当线程调用等待条件变量时,互斥锁会自动解锁,并且当其他线程/线程通知当前线程时被调用(退出等待),重新获取锁。
这种现象是违背的lock_guard
。lock_guard
只能构造一次,只能破坏一次。
因此lock_guard
不能与条件变量结合使用,但 aunique_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>
withcondition variable
As has been mentioned by others, std::unique_lock tracks the locked status of the mutex, so you can defer locking until after construction of the lock, and unlock before destruction of the lock. std::lock_guard does not permit this.
There seems no reason why the std::condition_variable wait functions should not take a lock_guard as well as a unique_lock, because whenever a wait ends (for whatever reason) the mutex is automatically reacquired so that would not cause any semantic violation. However according to the standard, to use a std::lock_guard with a condition variable you have to use a std::condition_variable_any instead of std::condition_variable.
Edit: deleted "Using the pthreads interface std::condition_variable and std::condition_variable_any should be identical". On looking at gcc's implementation: