C++ 获取两个互斥锁的锁并避免死锁

C++ 获取两个互斥锁的锁并避免死锁,c++,multithreading,deadlock,C++,Multithreading,Deadlock,下面的代码包含一个潜在的死锁,但似乎是必要的:为了安全地将数据从一个容器复制到另一个容器,必须锁定两个容器,以防止在另一个线程中发生更改 void foo::copy(const foo & rhs) { pMutex->lock(); rhs.pMutex->lock(); // do copy } Foo有一个STL容器,“docopy”本质上包括使用std::copy。如何在不引入死锁的情况下锁定两个互斥锁?对foo的实例施加某种总的顺序,并始

下面的代码包含一个潜在的死锁,但似乎是必要的:为了安全地将数据从一个容器复制到另一个容器,必须锁定两个容器,以防止在另一个线程中发生更改

void foo::copy(const foo & rhs)
{
    pMutex->lock();
    rhs.pMutex->lock();
    // do copy
}

Foo有一个STL容器,“docopy”本质上包括使用std::copy。如何在不引入死锁的情况下锁定两个互斥锁?

foo
的实例施加某种总的顺序,并始终以递增或递减的顺序获取它们的锁,例如,
foo1->lock()
然后
foo2->lock()

另一种方法是使用函数语义,而不是编写一个
foo::clone
方法来创建一个新实例,而不是关闭一个现有实例

如果您的代码正在执行大量锁定,您可能需要一个复杂的死锁避免算法,例如。

这如何

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%节省线程,您需要检查所有代码路径,然后用另一组眼睛再次检查,然后再次检查…

为避免死锁,最好等到两个资源都可以锁定:

不知道您使用的是哪个互斥API,因此这里有一些任意的伪代码,假设
can\u lock()
只检查它是否可以锁定互斥体,如果它确实锁定,
try\u 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
}

您可以尝试使用作用域锁定或自动锁定同时锁定两个互斥锁。。。。像银行转账一样

void Transfer(Receiver recv, Sender send)
{
    scoped_lock rlock(recv.mutex);
    scoper_lock slock(send.mutex);

    //do transaction.
}

这是一个已知的问题,已经有了std解决方案。
std::lock()
可以同时在两个或多个互斥体上调用,同时避免死锁。 它确实提供了一个建议

scoped_lock为这个函数提供了一个RAII包装,并且是 通常优先于对std::lock的裸调用


当然,这并不允许一个锁在另一个锁之上提前释放,所以使用
std::defer_lock
std::adoption_lock
,就像我在这篇文章中对类似问题所做的那样。

正如@Mellester提到的,您可以使用
std::lock
锁定多个互斥锁,以避免死锁

#include <mutex>

void foo::copy(const foo& rhs)
{
    std::lock(pMutex, rhs.pMutex);

    std::lock_guard<std::mutex> l1(pMutex, std::adopt_lock);
    std::lock_guard<std::mutex> l2(rhs.pMutex, std::adopt_lock);

    // do copy
}
#包括
void foo::复制(const foo和rhs)
{
标准:锁(pMutex,rhs.pMutex);
标准:锁紧装置l1(pMutex,标准:采用锁紧装置);
标准:锁紧装置l2(rhs.pMutex,标准:采用锁紧装置);
//抄袭
}

但请注意,检查
rhs
不是
*此
,因为在这种情况下,
std::lock
会由于锁定相同的互斥锁而导致UB。

即使是像此vs rhs地址这样简单的东西也可以工作。始终先锁定地址较低的那个。克隆只有在不复制的情况下才能正常工作,我认为隐式共享不起作用,但我会看一看。有趣的方法,凯尔。我看不出有任何缺陷。建议他制作一份数据的临时副本是一个很好的解决方案<然而,code>std::lock已经提供了这样一种死锁避免算法。为了避免死锁,最好引入一个活锁,这样会更具可读性吗?使用100%的CPU进行旋转?这是一种灾难的解决方法
std::lock
有一个死锁避免算法将它传递给两个互斥体,并且它比实现自己的互斥体对其他互斥体更具可读性。您为什么不先使用
std::unique\u lock
执行
std::defer\u lock
,然后执行
std::lock(l1,l2)?还是仅仅是个人喜好/风格?