c++ - 第一次锁定和创建 lock_guard(adopt_lock) 和创建 unique_lock(defer_lock) 和锁定有什么区别?

标签 c++ multithreading c++11 locking mutex

我找到了以下两段代码:

  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);
            // ...
        }   
    }
    
  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);
    }
    

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

最佳答案

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;
}   

这是一个标准的锁守卫,当退出作用域时,锁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;
    // ...
} 

这里我们首先创建锁而不获取它们(这是 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 deadlock !
    std::cout << e1.id << " and " << e2.id << " got locks" << std::endl;
    // ...
} 

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);
}

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

请注意 std::adopt_lock要求当前线程拥有互斥锁(这是因为我们刚刚锁定了它们)


结论

这里有两种模式:

1) 同时锁定两个互斥体,然后创建守卫

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

两种模式是等价的,目的是相同的:同时安全地锁定两个互斥体,并确保解锁总是发生在两者上。

至于std::lock_guardstd::unique_lock的区别,你应该看到this other SO post ,大多数时候 std::lock_guard 就足够了。

关于c++ - 第一次锁定和创建 lock_guard(adopt_lock) 和创建 unique_lock(defer_lock) 和锁定有什么区别?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27089434/

相关文章:

c++ - 一对<int,pair<int,int>>优先级队列的自定义比较器

c++ - 使用 Info-ZIP 3.0 源代码时出现编译器错误

C++:指向具有可变数量参数的函数的函数指针

linux - 非阻塞 I/O 问题

c++ - #include<boost> 没有这样的文件或目录

c++ - 按值传递与 const & 和 && 重载

c++ - 将txt文件加载到2个用逗号分隔的暗淡数组中 C++

c++ - 如何在多线程中同步 "for"循环计数器?

asp.net - 使用 ASP.NET 和 IIS 的多线程

c++ - NRVO 和 move