1.4. C++併發(線程之間共享數據)

多線程之間共享數據經常會產生競爭條件,當競爭條件破壞不變量時會導致問題的產生。比如多個線程對同一數據的修改可能會導致未定義行爲,多線程中某一行爲需要獲取多個鎖時可能會造成死鎖。解決競爭條件產生的問題的解決方案通常有兩個,一個是修改數據結構的設計,被稱爲無鎖編程,另一個是使用互斥元保護共享數據。
案例1. 利用互斥元保護列表之簡單實現

#include <list>
#include <mutex>
#include <algorithm>
std::list<int> some_list;
std::mutex some_mutex;
void add_to_list(int new_value)
{
	std::lock_guard<std::mutex> guard(some_mutex);
	some_list.push_back(new_value);
}
bool list_contains(int value_to_find)
{
	std::lock_guard<std::mutex> guard(some_mutex);
	return std::find(fome_list.begin(), some_list.end(), value_to_find) != some_list.end();
}

案列2. 避免在使用互斥元保護數據的時候,將數據的指針和引用傳遞到鎖的範圍之外。

class some_data;
class data_wrapper
{
private:
	some_data data;
	std::mutex m;
public:
	template<typename Function>
	void process_data(Function func)
	{
		std::lock_guard<std::mutex> l(m);
		func(data);                       //有風險,傳遞了受保護數據到保護範圍之外
	}
};
some_data* unprotected;
void malicious_function(some_data& protected_data)
{
	unprotected = &protected_data;
}
data_wrapper x;
void foo()
{
	x.process_data(malicious_function);   //傳遞了一個惡意函數
	unprotected->do_something();          //對受保護數據進行了未受保護的訪問
}

案列3. 一個線程安全棧定義

#include <exception>
#include <memory>
#include <mutex>
#include <stack>
struct empty_stack: std::exception
{
	const char* what() const throw();
};
template<typename T>
class threadsafe_stack
{
private:
	std::stack<T> data;
	mutable std::mutex m;
public:
	threadsafe_stack(){}
	threadsafe_stack(const threadsafe_stack& other)//拷貝構造函數
	{
		std::lock_guard<std::mutex> lock(other.m);
		data = other.data;
	}
	threadsafe_stack& operator = (const threadsafe_stack&) = delete; 
	void push(T new_value)
	{
		std::lock_guard<std::mutex> lock(m);
		data.push(new_value);
	}
	std::shared_ptr<T> pop()
	{
		std::lock_guard<std::mutex> lock(m);
		if(data.empty()) throw empty_stack();
		std::shared_ptr<T> const res(std::make_shared<T>(data.top()));
		data.pop();
		return res;
	}
	void pop(T& value)
	{
		std:lock_guard<std::mutex> lock(m);
		if(data.empty()) throw empty_stack();
		value = data.top();
		data.pop();
	}
	bool empty() const
	{
		std::lock_guard<std::mutex> lock(m);
		return data.empty()
	}
};

案例4. 在競爭條件中經常會出現死鎖,死鎖較常出現在需要鎖定兩個或以上互斥元的情況中。常見的建議是:1:使用相同的順序鎖定這兩個互斥元 2:避免嵌套鎖 3:在持有鎖時,避免調用用戶提供的代碼 4:使用鎖層次

class some_big_object;
void swap(some_big_object& lhs, some_big_object& rhs);
class X
{
private:
	some_big_object some_detail;
	std::mutex m;
public:
	X(some_big_object const& sd) :some_detail(sd){}
	friend void swap(X& lhs, X& rhs)
	{
		if(&lhs==&rhs)
			return;
		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);
	}
};

案例5. 使用層次鎖以及層次鎖的定義
獲取高層次鎖可以獲取底層次鎖,而獲取低層次鎖則不得獲取高層次鎖且會拋出異常

hierarchical_mutex high_level_mutex(10000);
hierarchical_mutex low_level_mutex(5000);
int do_low_level_stuff();
int low_level_func()
{
	std::lock_guard<hierarchical_mutex> lk(low_level_mutex);
	return do_low_level_stuff();
}
void high_level_stuff(int some_param);
void high_level_func()
{
	std::lock_guard<hierarchical_mutex> lk(high_level_mutex);
	high_level_stuff(low_level_func());
}

//線程a
void thread_a()
{
	high_level_func();   //高層次鎖下可以獲取低層次鎖
}

//線程b
hierarchical_mutex other_mutex(100);
void do_other_stuff()
{
	high_level_func();
	do_other_stuff();
}
void thread_b()
{
	std::lock_guard<hierarchical_mutex> lk(other_mutex);
	other_stuff();  //低層次鎖下無法獲得高層次鎖
}
//層次鎖的實現
class hierarchical_mutex
{
	std::mutex internal_mutex;
	unsigned long const hierarchy_value;
	unsigned long previous_hierarchy_value;
	static thread_local unsigned long this_thread_hierarchy_value;  //使用靜態成員變量來標識當前線程的層次值
	
	void check_for_hierarchy_violation()
	{
		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):
		hierarchy_value(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.lock();
		update_hierarchy_value();
	}
	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(ULONG_MAX); //靜態成員變量需要初始化

案例6. 更靈活的RAII風格鎖,std::unique_lock
std::lock_guard是比較簡單的RAII風格鎖,並且可以傳入std::adopt_lock作爲第二個參數,表示已經上鎖,使用起來十分的方便。另外,還有更加靈活的鎖,std::unique_lock可以主動進行上鎖 lock()和解鎖unlock(),也能使用std::defer_lock()表示互斥元在構造時未上鎖。在擁有了靈活性的同時,損失了一些性能。主要體現在std::unique_lock實現了lock()、try_lock()和unlock(),他們會調用底層上互斥元的同名函數去做實際工作,並且其實例內部有一個標識來表示該實例是否擁有此互斥元,只有在擁有互斥元,其析構的時候才能調用unlock()。因此,其犧牲了性能以獲取易用性和靈活性。

class some_big_object;
void swap(some_big_object& lhs, some_big_object& rhs);
class X
{
private:
	some_big_object some_detail;
	std::mutex m;
public:
	X(some_big_object const& sd):some_detail(sd){}
	friend void swap(X& lhs, X& rhs)
	{
		if(&lhs==&rhs)
			return;
		std::unique_lock<std::mutex> lock_a(lhs.m, std::defer_lock); //定時的時候,互斥元未上鎖
		std::unique_lock<std::mutex> lock_b(rhs.m, std::defer_lock);
		std::lock(lock_a, lock_b);   //多兩個互斥元同時上鎖
		swap(lhs.some_detail, rhs.some_detail);
	}
};

案例7. 在作用於之間轉移鎖的所有權
std::unique_lock是可移動但不可複製的,如果被轉移實例是右值即臨時量,那麼可以自動進行轉移(隱式),否則需要顯式地進行轉移

std::unique_lock<std::mutex> get_lock()
{
	extern std::mutex some_mutex;
	std::unique_lock<std::mutex> lk(some_mutex);
	prepare_data();
	return  lk;
}
void process_data()
{
	std::unique_lock<std::mutex> lk(gete_lock)); //右值,可以直接進行轉移所有權
	do_something();
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章