【问题标题】:Acquire a lock on two mutexes and avoid deadlock获取两个互斥锁上的锁并避免死锁
【发布时间】:2011-01-16 00:20:20
【问题描述】:

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

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

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

【问题讨论】:

  • std::lock 有一个避免死锁的算法将两个互斥锁都传递给它,它对于其他人来说比实现自己的更具可读性。

标签: c++ multithreading deadlock


【解决方案1】:

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

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

如果您的代码执行大量锁定,您可能需要复杂的死锁避免算法,例如banker's algorithm

【讨论】:

  • 即使像 this vs rhs 的地址这样简单的东西也可以工作。总是先锁定地址较低的那个。
  • 克隆只有在不复制的情况下才能正常工作,而且我认为隐式共享不会起作用,但我会看看。有趣的方法凯尔。我看不出任何缺陷。
  • 建议他制作数据的临时副本是一个很好的解决方案。然而std::lock 已经提供了这样一种避免死锁的算法。并且更具可读性
【解决方案2】:

这个怎么样?

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

【讨论】:

    【解决方案3】:

    正如@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
    }
    

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

    【讨论】:

    • 有什么理由你不会先用std::unique_lock&lt;std::mutex&gt;std::defer_lock 然后再做std::lock(l1,l2); ?还是只是个人喜好/风格?
    【解决方案4】:

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

    std::scoped_lock 为这个函数提供了一个 RAII 包装器,并且是 通常比对 std::lock 的裸调用更喜欢。

    当然,这实际上不允许提前释放一个锁在另一个之上,因此请使用 std::defer_lockstd::adopt_lock,就像我在 answer 中对类似问题所做的那样。

    【讨论】:

      【解决方案5】:

      为了避免死锁,最好的办法是等到两个资源都被锁定:

      不知道您使用的是哪个互斥体 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
      }
      

      【讨论】:

      • 为了避免死锁,最好引入活锁?然后旋转,使用 100% CPU?
      【解决方案6】:

      您可以尝试使用 scoped_lock 或 auto_lock 同时锁定两个互斥锁......就像银行转账一样......

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

      【讨论】:

      • 这是灾难的秘诀
      猜你喜欢
      • 2015-10-26
      • 1970-01-01
      • 1970-01-01
      • 2011-02-14
      • 1970-01-01
      • 1970-01-01
      • 2018-06-10
      • 2011-03-03
      • 1970-01-01
      相关资源
      最近更新 更多