[英]Deadlock simulation using std::mutex
我有以下示例:
template <typename T>
class container
{
public:
std::mutex _lock;
std::set<T> _elements;
void add(T element)
{
_elements.insert(element);
}
void remove(T element)
{
_elements.erase(element);
}
};
void exchange(container<int>& cont1, container<int>& cont2, int value)
{
cont1._lock.lock();
std::this_thread::sleep_for(std::chrono::seconds(1));
cont2._lock.lock();
cont1.remove(value);
cont2.add(value);
cont1._lock.unlock();
cont2._lock.unlock();
}
int main()
{
container<int> cont1, cont2;
cont1.add(1);
cont2.add(2);
std::thread t1(exchange, std::ref(cont1), std::ref(cont2), 1);
std::thread t2(exchange, std::ref(cont2), std::ref(cont1), 2);
t1.join();
t2.join();
return 0;
}
在这种情况下,我正在陷入僵局。 但是当我使用std :: lock_guard而不是手动锁定和解锁互斥锁时,我没有死锁。 为什么?
void exchange(container<int>& cont1, container<int>& cont2, int value)
{
std::lock_guard<std::mutex>(cont1._lock);
std::this_thread::sleep_for(std::chrono::seconds(1));
std::lock_guard<std::mutex>(cont2._lock);
cont1.remove(value);
cont2.add(value);
}
您的两个代码段无法比较。 第二个代码段锁定并立即解锁每个互斥锁,因为临时lock_guard对象在分号处被销毁:
std::lock_guard<std::mutex>(cont1._lock); // temporary object
使用锁定保护的正确方法是制作它们的范围变量 :
{
std::lock_guard<std::mutex> lock(my_mutex);
// critical section here
} // end of critical section, "lock" is destroyed, calling mutex.unlock()
(请注意,还有另一个常见错误,它们相似但不同:
std::mutex mu;
// ...
std::lock_guard(mu);
这声明了一个名为mu
的变量 (就像int(n);
)。 但是,此代码std::lock_guard
,因为std::lock_guard
没有默认构造函数。 但它会用std::unique_lock
,它也不会最终锁定任何内容。)
现在解决实际问题:如何以一致的顺序一次锁定多个互斥锁? 在整个代码库中,甚至在未来用户的代码库中,或者甚至在本地案例中,就单个锁定订单达成一致可能是不可行的。 在这种情况下,请使用std::lock
算法:
std::mutex mu1;
std::mutex mu2;
void f()
{
std::lock(mu1, mu2);
// order below does not matter
std::lock_guard<std::mutex> lock1(mu1, std::adopt_lock);
std::lock_guard<std::mutex> lock2(mu2, std::adopt_lock);
}
在C ++ 17中,有一个名为scoped_lock
的新的可变参数锁定保护模板:
void f_17()
{
std::scoped_lock lock(mu1, mu2);
// ...
}
scoped_lock
的构造scoped_lock
使用与std::lock
相同的算法,因此两者可以兼容使用。
虽然Kerrek SB的答案是完全有效的,但我以为我会在戒指中扔另一顶帽子。 从性能的角度来看,应该将std::lock
或任何尝试和撤退死锁避免策略视为最后的手段。
怎么样:
#include <functional> //includes std::less<T> template.
static const std::less<void*> l;//comparison object. See note.
void exchange(container<int>& cont1, container<int>& cont2, int value)
{
if(&cont1==&cont2) {
return; //aliasing protection.
}
std::unique_lock<std::mutex> lock1(cont1._lock, std::defer_lock);
std::unique_lock<std::mutex> lock2(cont2._lock, std::defer_lock);
if(l(&cont1,&cont2)){//in effect portal &cont1<&cont2
lock1.lock();
std::this_thread::sleep_for(std::chrono::seconds(1));
lock2.lock();
}else{
lock2.lock();
std::this_thread::sleep_for(std::chrono::seconds(1));
lock1.lock();
}
cont1.remove(value);
cont2.add(value);
}
此代码使用对象的内存地址来确定任意但一致的锁定顺序。 这种方法(当然)可以概括。
另请注意,在可重用代码中,别名保护是必要的,因为cont1为cont2的版本会因尝试锁定两次相同的锁而无效。 std::mutex
不能被认为是递归锁定,通常不是。
注意:使用std::less<void>
可确保合规性,因为它可确保地址的总排序一致。 技术上(&cont1 <&cont2)是未指明的行为。 谢谢Kerrek SB!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.