[C++11 併發程式設計] 07
假設有兩個執行緒,在執行某些操作時,都需要鎖定一對mutex,執行緒A鎖定了mutex A,而執行緒B鎖定了額mutex B,它們都在等待對方釋放另一個mutex,這就會導致這兩個執行緒都無法繼續執行。這種情況就是死鎖。
避免死鎖最簡單的方法是總是以相同的順序對兩個mutex進行鎖定,比如總是在鎖定mutex B之前鎖定mutex A,就永遠都不會死鎖。
假設有一個操作要交換同一個類的兩個例項的內容,為了交換操作不被併發修改影響,我們需要鎖定這兩個例項內部的mutex。但是,如果選定一個固定的順序來鎖定mutex(線鎖定第一個引數指定物件的mutex,再鎖定第二個引數指定物件的mutex)恰恰適得其反,會導致死鎖。
針對這種情況,我們需要使用C++11標準庫的std::lock操作來解決這個問題。lock函式可以接受兩個或者多個mutex以避免死鎖。
示例如下:
使用std::lock_guard確保在程式丟擲異常對出時,也能正確的對鎖定的mutex進行解鎖。#include <mutex> class some_big_object {}; void swap(some_big_object& lhs,some_big_object& rhs) {} class X { private: some_big_object some_detail; mutable std::mutex m; public: X(some_big_object const& sd):some_detail(sd){} friend void swap(X& lhs, X& rhs) { // 判斷兩個例項是否相同,如果相同則直接退出 // 因為鎖定已經被本執行緒鎖定的mutex的結果是不確定的 if(&lhs==&rhs) return; // 鎖定兩個mutex std::lock(lhs.m,rhs.m); // 建立兩個lock_guard例項,分別給它們傳入一個mutex物件 // std::adopt_lock引數表明所傳入的mutex已經被鎖定了,它們只需要接受已鎖定mutex的所有權 // 而不需要在它的建構函式中嘗試鎖定這個傳入的mutex 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); } }; int main() {}
std::lock操作可以保證在成功鎖定第一個mutex後,如果在嘗試鎖定第二個mutex時發生異常,第一個mutex會被自動解鎖。std::lock操作只能在鎖定多個mutex時幫助我們避免死鎖,如果這些mutex是一個個被單獨鎖定的,就需要我們自己在實現的時候保證程式不會發生死鎖。接下來我們看看,避免死鎖的幾個基本方法:
1. 避免巢狀鎖:
如果每個執行緒都只能鎖定一個mutex,則不會發生死鎖。如果要使用多個鎖,就使用std::lock。
2. 避免在鎖定了一個mutex後呼叫使用者提供的程式碼:
我們無法保證使用者程式碼做了什麼,其很有可能鎖定其它的mutex而導致死鎖。
3. 以固定的順序鎖定mutex:
如果確實需要鎖定多個mutex,而這些mutex可以被同時鎖定,使用前面我們講到std::lock方法來保證鎖定mutex的順序而避免死鎖。如果這些mutex只能分別被鎖定,則需要在實現時保證鎖定mutex的順序是固定的,比如總是在鎖定B之前鎖定A,在鎖定C之前鎖定B。
4. 使用分層鎖:
把程式分成幾個層次。區分每個層次中使用的鎖,當一個執行緒已經持有更低層次的鎖時,不允許使用高層次的鎖。可以在程式執行時給不同的鎖加上層次號,記錄每個執行緒持有的鎖。
#include <stdexcept>
#include<thread>
#include<mutex>
class hierarchical_mutex//實現mutex的三個介面lock,unlock,trylock
{
std::mutex internal_mutex;
unsigned long const hierarchy_value;//記錄mutex所在層次
unsigned long previous_hierarchy_value;//記錄上一個mutex的層次,解鎖時恢復執行緒的層次
//thread_local每一個執行緒都有自己的該全域性變數的例項(instance)
static thread_local unsigned long this_thread_hierarchy_value;//執行緒所在層次,是thread_local
void check_for_hierarchy_violation()
{ //執行緒所在層次要大於當前的mutex的層次,否則丟擲異常
if (this_thread_hierarchy_value <= hierarchy_value)
{
throw std::logic_error("mutex hierarchy violated");
}
}
void update_hierarchy_value()
{
previous_hierarchy_value = this_thread_hierarchy_value;
this_thread_hierarchy_value = hierarchy_value;
}
public:
explicit hierarchical_mutex(unsigned long value) :
hierarchical_mutex(value), previous_hierarchy_value(0){}
void lock()
{
//先檢查、再上鎖、再更新層次
check_for_hierarchy_violation();
internal_mutex.lock();
update_hierarchy_value();
}
void unlock()
{
//更新層次、再解鎖
this_thread_hierarchy_value = previous_hierarchy_value;
internal_mutex.unlock();
}
bool try_lock()
{
check_for_hierarchy_violation();
if (!internal_mutex.try_lock())
return false;
update_hierarchy_value();
return true;
}
};
thread_local unsigned long hierarchical_mutex::this_thread_hierarchy_value(ULLONG_MAX);