以下代码包含潜在的死锁,但似乎是必要的:要将数据安全地从另一个容器复制到一个容器,必须锁定这两个容器以防止在另一个线程中发生更改。
void foo::copy(const foo & rhs)
{
pMutex->lock();
rhs.pMutex->lock();
// do copy
}
Foo有一个STL容器,“do copy”主要包括使用std :: copy。如何在不引入死锁的情况下锁定两个互斥锁?
在foo
的实例上施加某种总顺序,并且总是以增加或减少的顺序获得它们的锁,例如foo1->lock()
然后foo2->lock()
。
另一种方法是使用函数语义,而是编写一个foo::clone
方法,该方法创建一个新实例而不是破坏现有实例。
如果您的代码执行大量锁定,则可能需要复杂的死锁避免算法,例如banker's algorithm。
这个怎么样?
void foo::copy(const foo & rhs)
{
scopedLock lock(rhs.pMutex); // release mutex in destructor
foo tmp(rhs);
swap(tmp); // no throw swap locked internally
}
这是异常安全的,并且非常安全。要100%线程保存,您需要检查所有代码路径,然后再用另一组眼睛重新审核,之后再次审核...
这是一个已知的问题,已经有一个标准的解决方案。 std::lock()
可以同时在2个或更多个互斥锁上调用,同时避免死锁。 More information here确实提供了推荐。
std :: scoped_lock为此函数提供了一个RAII包装器,通常优先于对std :: lock的裸调用。
当然这并不能真正允许一个锁定的早期释放高于另一个锁定所以使用std::defer_lock
或std::adopt_lock
,就像我在这个answer中所做的那样使用类似的问题。
为了避免死锁,最好等到两个资源都可以锁定:
不知道你正在使用哪个互斥API,所以这里有一些任意的伪代码,假设can_lock()
只检查它是否可以锁定互斥锁,并且try_lock()
如果锁定则返回true,如果互斥锁已经被其他人锁定,则返回false 。
void foo::copy(const foo & rhs)
{
for(;;)
{
if(! pMutex->cany_lock() || ! rhs.pMutex->cany_lock())
{
// Depending on your environment call or dont call sleep()
continue;
}
if(! pMutex->try_lock())
continue;
if(! rhs.pMutex->try_lock())
{
pMutex->try_lock()
continue;
}
break;
}
// do copy
}
您可以尝试使用scoped_lock或auto_lock来同时锁定两个互斥锁....就像银行转账一样......
void Transfer(Receiver recv, Sender send)
{
scoped_lock rlock(recv.mutex);
scoper_lock slock(send.mutex);
//do transaction.
}