我找到了以下2段代码:
http://en.cppreference.com/w/cpp/thread/lock
void assign_lunch_partner(Employee &e1, Employee &e2)
{
// use std::lock to acquire two locks without worrying about
// other calls to assign_lunch_partner deadlocking us
{
// m is the std::mutex field
std::unique_lock<std::mutex> lk1(e1.m, std::defer_lock);
std::unique_lock<std::mutex> lk2(e2.m, std::defer_lock);
std::lock(lk1, lk2);
// ...
}
}
http://www.amazon.com/C-Concurrency-Action-Practical-Multithreading/dp/1933988770
void swap(X& lhs, X&rhs){
if(&lhs == &rhs)
return;
// m is the std::mutex field
std::lock(lhs.m, rhs.m);
std::lock_guard<std::mutex> lock_a(lhs.m, std::adopt_lock);
std::lock_guard<std::mutex> lock_b(rhs.m, std::adopt_lock);
swap(lhs.some_detail, rhs.some_detail);
}
我想问一下使用2个版本中的任何一个有什么区别和后果? (先锁定或首先创建std::lock_guard
或std::unique_lock
?)
答案 0 :(得分:12)
1)第一个代码示例
{
static std::mutex io_mutex;
std::lock_guard<std::mutex> lk(io_mutex);
std::cout << e1.id << " and " << e2.id << " are waiting for locks" << std::endl;
}
这是一个标准的锁定防护,当退出范围时,锁lk
被释放
{
std::unique_lock<std::mutex> lk1(e1.m, std::defer_lock);
std::unique_lock<std::mutex> lk2(e2.m, std::defer_lock);
std::lock(lk1, lk2);
std::cout << e1.id << " and " << e2.id << " got locks" << std::endl;
// ...
}
这里我们首先创建锁而不获取它们(这是std::defer_lock
的点)然后,同时在两个锁上使用std::lock
确保它们被获取而没有死锁的风险,如果另一个函数交错的调用者(如果用两次连续调用std::lock
替换它,我们可能会出现死锁:
{
std::unique_lock<std::mutex> lk1(e1.m, std::defer_lock);
std::unique_lock<std::mutex> lk2(e2.m, std::defer_lock);
std::lock(lk1);
std::lock(lk2); // Risk of dedalock !
std::cout << e1.id << " and " << e2.id << " got locks" << std::endl;
// ...
}
2)第二个代码示例
void swap(X& lhs, X&rhs){
if(&lhs == &rhs)
return;
// m is the std::mutex field
std::lock(lhs.m, rhs.m);
std::lock_guard<std::mutex> lock_a(lhs.m, std::adopt_lock);
std::lock_guard<std::mutex> lock_b(rhs.m, std::adopt_lock);
swap(lhs.some_detail, rhs.some_detail);
}
现在,我们首先获取锁(仍然避免死锁),然后然后我们创建锁定以确保它们被正确释放。
请注意std::adopt_lock
要求当前线程拥有互斥锁(因为我们只是锁定它们就是这种情况)
<强>结论强>
这里有两种模式:
1)同时锁定两个互斥锁,然后创建防护装置
2)创建警卫,然后同时锁定两个互斥锁
两种模式都是等效的,并且针对同一件事:安全地同时锁定两个互斥锁,并确保两者都始终解锁。
至于std::lock_guard
和std::unique_lock
之间的差异,您应该看到this other SO post,大部分时间std::lock_guard
已足够。
答案 1 :(得分:7)
book中实际上有一段(3.2.6)解释说代码实际上是等价的,你可以用另一个代替。唯一的区别是std::unique_lock
往往占用更多空间,比std::lock_guard
慢一点。
无论何时您不需要std::unique_lock
提供的额外灵活性,请与std::lock_guard
一起使用。
答案 2 :(得分:5)
区别在于对未来变化的稳健性。在adopt_lock
版本中,有一个窗口,其中互斥锁被锁定但不归清理处理程序所有:
std::lock(lhs.m, rhs.m);
// <-- Bad news if someone adds junk here that can throw.
std::lock_guard<std::mutex> lock_a(lhs.m, std::adopt_lock);
std::lock_guard<std::mutex> lock_b(rhs.m, std::adopt_lock);
在没有任何编译时错误的情况下,也可能意外删除/省略其中一个保护声明。当死锁命中时,问题在运行时会很明显,但将死锁追溯到其来源并不好玩。
defer_lock
版本不会遇到这些问题。由于在锁定发生之前声明了保护对象,因此没有不安全的窗口。当然,如果您省略/删除其中一个保护声明,您将在std::lock
电话中收到编译错误。