首次锁定和创建lock_guard(adopt_lock)以及创建unique_lock(defer_lock)和锁定之间的区别是什么?

Pat*_*ryk 16 c++ multithreading mutex locking c++11

我找到了以下2段代码:

  1. http://en.cppreference.com/w/cpp/thread/lock

    void assign_lunch_partner(Employee &e1, Employee &e2)                                                                                                  
    {   
        // use std::lock to acquire two locks without worrying about 
        // other calls to assign_lunch_partner deadlocking us
        {   
            // m is the std::mutex field
            std::unique_lock<std::mutex> lk1(e1.m, std::defer_lock);
            std::unique_lock<std::mutex> lk2(e2.m, std::defer_lock);
            std::lock(lk1, lk2);
            // ...
        }   
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. http://www.amazon.com/C-Concurrency-Action-Practical-Multithreading/dp/1933988770

    void swap(X& lhs, X&rhs){                                                                                                                              
      if(&lhs == &rhs)
        return;
      // m is the std::mutex field
      std::lock(lhs.m, rhs.m);
      std::lock_guard<std::mutex> lock_a(lhs.m, std::adopt_lock);
      std::lock_guard<std::mutex> lock_b(rhs.m, std::adopt_lock);
      swap(lhs.some_detail, rhs.some_detail);
    }
    
    Run Code Online (Sandbox Code Playgroud)

我想问一下使用2个版本中的任何一个有什么区别和后果?(先锁定或首先创建std::lock_guardstd::unique_lock?)

qua*_*dev 15

1)第一个代码示例

{   
    static std::mutex io_mutex;
    std::lock_guard<std::mutex> lk(io_mutex);
    std::cout << e1.id << " and " << e2.id << " are waiting for locks" << std::endl;
}   
Run Code Online (Sandbox Code Playgroud)

这是一个标准的锁定保护装置,当退出示波器时,锁定lk被释放

{   
    std::unique_lock<std::mutex> lk1(e1.m, std::defer_lock);
    std::unique_lock<std::mutex> lk2(e2.m, std::defer_lock);
    std::lock(lk1, lk2);
    std::cout << e1.id << " and " << e2.id << " got locks" << std::endl;
    // ...
} 
Run Code Online (Sandbox Code Playgroud)

在这里,我们第一次没有获得他们创造的锁(这点std::defer_lock),然后,用std::lock在两个锁同时确保它们没有死锁的风险获取如果函数交错的另一个调用者(我们可以,如果你有一个僵局取而代之的是连续两次调用std::lock:

{   
    std::unique_lock<std::mutex> lk1(e1.m, std::defer_lock);
    std::unique_lock<std::mutex> lk2(e2.m, std::defer_lock);
    std::lock(lk1);
    std::lock(lk2); // Risk of dedalock !
    std::cout << e1.id << " and " << e2.id << " got locks" << std::endl;
    // ...
} 
Run Code Online (Sandbox Code Playgroud)

2)第二个代码示例

void swap(X& lhs, X&rhs){                                                                                                                              
  if(&lhs == &rhs)
    return;
  // m is the std::mutex field
  std::lock(lhs.m, rhs.m);
  std::lock_guard<std::mutex> lock_a(lhs.m, std::adopt_lock);
  std::lock_guard<std::mutex> lock_b(rhs.m, std::adopt_lock);
  swap(lhs.some_detail, rhs.some_detail);
}
Run Code Online (Sandbox Code Playgroud)

现在,我们首先获取锁(仍然避免死锁),然后我们创建锁定以确保它们被正确释放.

请注意,std::adopt_lock要求当前线程拥有互斥锁(因为我们只是锁定它们就是这种情况)


结论

这里有2种模式:

1)同时锁定两个互斥锁,然后创建防护装置

2)创建警卫,然后同时锁定两个互斥锁

两种模式都是等效的,并且针对同一件事:同时安全地锁定两个互斥锁,并确保两者都始终解锁.

至于std::lock_guard和之间的区别std::unique_lock,你应该看到这个其他的SO帖子,大部分时间std::lock_guard都足够了.


Jiř*_*šil 7

实际上,有在一个段落(3.2.6)的书解释该代码是几乎等同,你可以更换一个与其他.唯一的区别是,它std::unique_lock往往占用更多的空间,而且比它慢一点std::lock_guard.

最重要的是,无论何时您不需要std::unique_lock提供额外的灵活性,请继续使用std::lock_guard.


Cas*_*sey 5

区别在于对未来更改的鲁棒性。在该adopt_lock版本中,有一个窗口,其中互斥锁被锁定但未被清理处理程序拥有:

std::lock(lhs.m, rhs.m);
// <-- Bad news if someone adds junk here that can throw.
std::lock_guard<std::mutex> lock_a(lhs.m, std::adopt_lock);
std::lock_guard<std::mutex> lock_b(rhs.m, std::adopt_lock);
Run Code Online (Sandbox Code Playgroud)

也有可能意外删除/忽略了一个保护声明,而没有任何编译时错误。当遇到死锁时,该问题在运行时将很明显,但是将死锁追溯到其源头并不是一件有趣的事情。

defer_lock版本不会遇到这些问题。由于保护对象是锁定发生之前声明的,因此没有不安全的窗口。当然,如果您省略/删除其中一个保护声明,则在std::lock调用时会出现编译器错误。

  • 在 C++17 中,[`std::scoped_lock`](http://en.cppreference.com/w/cpp/thread/scoped_lock) 可以更优雅地解决这两个问题。 (3认同)