1. 程式人生 > >[C++11 併發程式設計] 07

[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以避免死鎖。

示例如下:

#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_guard確保在程式丟擲異常對出時,也能正確的對鎖定的mutex進行解鎖。

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