多線程之間共享資料經常會産生競争條件,當競争條件破壞不變量時會導緻問題的産生。比如多個線程對同一資料的修改可能會導緻未定義行為,多線程中某一行為需要擷取多個鎖時可能會造成死鎖。解決競争條件産生的問題的解決方案通常有兩個,一個是修改資料結構的設計,被稱為無鎖程式設計,另一個是使用互斥元保護共享資料。
案例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();
}