2014-11-23 48 views
10

我发现了以下2件代码:第一次锁定和创建lock_guard(adopt_lock)并创建unique_lock(defer_lock)和锁定有什么区别?

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

我要问的是什么的差异和后果使用2种版本中(首先锁定或首先创建std::lock_guardstd::unique_lock?)

回答

11

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 dedalock ! 
    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要求当前线程拥有互斥量(这是因为我们只是把他们锁的情况下)


结论

有2种模式的位置:

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

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

两种模式都是等价的,并且瞄准同一件事情:同时安全地锁定两个互斥锁,并确保两个互斥锁始终都会发生。

至于std::lock_guardstd::unique_lock之间的差异,你应该看到this other SO post,大部分时间std::lock_guard就足够了。

7

book实际上有一段(3.2.6)解释代码几乎相同,您可以用另一个代替。唯一的区别是std::unique_lock倾向于占用更多空间,并且比std::lock_guard慢。

底线是每当您不需要std::unique_lock提供的额外灵活性时,请使用std::lock_guard

4

不同之处在于对未来变化的稳健性。在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); 

它也可能意外地删除/省略保护声明的一个没有任何编译时错误。在死锁触发时,问题在运行时显而易见,但将死锁追溯到源代码并不是件好事。

defer_lock版本不会遇到这些问题。由于警卫对象在之前被宣布为,所以锁定发生,没有不安全的窗口。当然,如果您省略/移除其中一个警戒声明,您将在std::lock调用中收到编译器错误。

+1

在C++ 17中,['std :: scoped_lock'](http://en.cppreference.com/w/cpp/thread/scoped_lock)会更加优雅地解决这两个问题。 – Nawaz 2017-01-18 17:10:18

相关问题