C++ 使用std::mutex模拟死锁
我有以下例子:C++ 使用std::mutex模拟死锁,c++,mutex,stdthread,C++,Mutex,Stdthread,我有以下例子: 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(ele
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::互斥锁;
std::set\u元素;
无效添加(T元素)
{
_要素。插入(要素);
}
脱空(T元件)
{
_元素。擦除(元素);
}
};
无效交换(容器和cont1、容器和cont2、int值)
{
续1._lock.lock();
std::this_thread::sleep_for(std::chrono::seconds(1));
续2._lock.lock();
cont1.删除(值);
续2.增加(价值);
续1._lock.unlock();
续2._lock.unlock();
}
int main()
{
集装箱cont1,cont2;
续1.增加(1);
续2.加入(2);
线程t1(交换,std::ref(cont1),std::ref(cont2),1);
线程t2(交换,std::ref(cont2),std::ref(cont1),2);
t1.join();
t2.连接();
返回0;
}
在这种情况下,我将面临僵局。但是当我使用std::lock_guard而不是手动锁定和解锁mutext时,我没有死锁。为什么?
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);
}
void交换(container&cont1、container&cont2、int值)
{
标准:锁紧装置(续1锁紧装置);
std::this_thread::sleep_for(std::chrono::seconds(1));
标准:锁紧装置(续2);
cont1.删除(值);
续2.增加(价值);
}
您的两个代码片段不可比较。第二个代码段锁定并立即解锁每个互斥锁,因为临时锁保护对象在分号处被销毁:
std::lock_guard<std::mutex>(cont1._lock); // temporary object
这声明了一个名为mu
(与int(n);
)的变量。但是,此代码的格式不正确,因为std::lock\u 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);
}
scoped_lock
的构造函数使用了与std::lock
相同的算法,因此两者可以兼容使用。虽然Kerrek SB的答案完全正确,但我认为我应该另辟蹊径<代码>标准::锁或任何尝试和退出死锁避免策略都应该被视为性能角度的最后手段
那么:
#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);
}
#include//includest::less模板。
静态常数std::小于l//比较对象。见附注。
无效交换(容器和cont1、容器和cont2、int值)
{
如果(&cont1==&cont2){
return;//别名保护。
}
std::unique_lock lock1(cont1._lock,std::defer_lock);
std::唯一锁定锁定2(续2.\u锁定,std::延迟锁定);
如果(l(&cont1,&cont2)){//实际上是portal&cont1Hmmm,我没有看到(没有读到,只是假设他正确地使用了lock_-guard)。我可以建议将“不可行”改为“不可行”吗?重要的问题是嵌套顺序而不是锁定顺序,很可能实现多个协作库,这些库在进入时锁定对象,在退出时解锁对象,并且从不冲突。这种策略的典型失败是多态性。一旦多态性方法显式启动,或者(更糟!)隐式地获取锁,而调用方也在这样做,这只鹅很可能是按照规定的锁定顺序烹调的。@DanAllen:嗯,我已经说过“跨越整个代码库”;在特定情况下就订单达成一致当然是可行的。OP的示例表明,订单与嵌套一样重要。也许最好将嵌套作为问题的另一个来源来具体提及?唯一的死锁问题是嵌套。嵌套指的是保留一个(或多个)锁定和寻找他人。我并不反对你的观点,但这意味着所有大型代码库都需要尝试退出反死锁策略。我只是认为这太消极了。我的观点是,只要单个子系统保持自己的状态,就不必有问题。问题就在何时(例如多态性)保持“自己对自己”会让你遇到麻烦……这并不能直接回答你的问题,但你可以通过使用同时锁定两个互斥锁来避免死锁。你可以(也应该)仍然使用std::adopt_lock
@Amadeusz将锁的所有权转移给lock\u-guard
。为什么要切换参数?这就是造成死锁的原因(或者是故意模拟死锁)@Wernerrasmus:提示:请参阅问题标题:-)&cont1<&cont2
有未定义的行为,不幸的是:-您的cast编译了吗?下面是一篇非常详细的论文,它测量了各种多锁锁定策略的性能,包括这里建议的“排序”算法:在高争用场景下,“排序”不是高性能的解决方案。@HowardHinnant尝试一下。#在顶部定义模型1或2。这是一个玩具,也是另一个问题和平台特定的情况,但有序锁模型比std::lock
快得多。如果将其修剪回2个线程(作为OP)影响仍然存在,但有所减弱。这是可怜的哲学家问题!哲学家比餐具多!PS:只是试图指出一个模型和一组测量值不应该过度外推。测量值很好!我被要求比这个测试的std::lock
速度快20%左右。我稍微修改了一下测试,使其具有4个容器和sett1{c1,c2}
,t2{c2,c3}
,t3{c3,c4}
,t4{c4,c1}
。结果变为std::lock
比ordered快330%。这可以用“ordered可能容易受到互斥体之间的循环依赖性的影响”来概括。我还注意到
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);
}
void f_17()
{
std::scoped_lock lock(mu1, mu2);
// ...
}
#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);
}