1. 多個線程訪問同一資源時,爲了保證數據的一致性,最簡單的方式就是使用 mutex(互斥鎖)。ios
引用 cppreference 的介紹:緩存
The mutex class is a synchronization primitive that can be used to protect shared data from being simultaneously accessed by multiple threads.
方法1:直接操做 mutex,即直接調用 mutex 的 lock / unlock
函數
此例順帶使用了 boost::thread_group
來建立一組線程。安全
#include <iostream> #include <boost/thread/mutex.hpp> #include <boost/thread/thread.hpp> boost::mutex mutex; int count = 0; void Counter() { mutex.lock(); int i = ++count; std::cout << "count == " << i << std::endl; // 前面代碼若有異常,unlock 就調不到了。 mutex.unlock(); } int main() { // 建立一組線程。 boost::thread_group threads; for (int i = 0; i < 4; ++i) { threads.create_thread(&Counter); } // 等待全部線程結束。 threads.join_all(); return 0; }
方法2:使用 lock_guard
自動加鎖、解鎖。原理是 RAII,和智能指針相似數據結構
#include <iostream> #include <boost/thread/lock_guard.hpp> #include <boost/thread/mutex.hpp> #include <boost/thread/thread.hpp> boost::mutex mutex; int count = 0; void Counter() { // lock_guard 在構造函數里加鎖,在析構函數裏解鎖。 boost::lock_guard<boost::mutex> lock(mutex); int i = ++count; std::cout << "count == " << i << std::endl; } int main() { boost::thread_group threads; for (int i = 0; i < 4; ++i) { threads.create_thread(&Counter); } threads.join_all(); return 0; }
方法3:使用 unique_lock
自動加鎖、解鎖unique_lock
與 lock_guard
原理相同,可是提供了更多功能(好比能夠結合條件變量使用)。
注意:mutex::scoped_lock
其實就是 unique_lock<mutex>
的 typedef
。併發
#include <iostream> #include <boost/thread/mutex.hpp> #include <boost/thread/thread.hpp> boost::mutex mutex; int count = 0; void Counter() { boost::unique_lock<boost::mutex> lock(mutex); int i = ++count; std::cout << "count == " << i << std::endl; } int main() { boost::thread_group threads; for (int i = 0; i < 4; ++i) { threads.create_thread(&Counter); } threads.join_all(); return 0; }
方法4:爲輸出流使用單獨的 mutex
這麼作是由於 IO 流並非線程安全的!
若是不對 IO 進行同步,此例的輸出極可能變成:函數
count == count == 2count == 41 count == 3
由於在下面這條輸出語句中:atom
std::cout << "count == " << i << std::endl;
輸出 "count == " 和 i 這兩個動做不是原子性的(atomic),可能被其餘線程打斷。spa
#include <iostream> #include <boost/thread/mutex.hpp> #include <boost/thread/thread.hpp> #include <boost/thread/lock_guard.hpp> boost::mutex mutex; boost::mutex io_mutex; int count = 0; void Counter() { int i; { boost::unique_lock<boost::mutex> lock(mutex); i = ++count; } { boost::unique_lock<boost::mutex> lock(io_mutex); std::cout << "count == " << i << std::endl; } } int main() { boost::thread_group threads; for (int i = 0; i < 4; ++i) { threads.create_thread(&Counter); } threads.join_all(); return 0; }
2. 保護共享數據的替代設施線程
2.1 保護共享數據的初始化過程指針
醜陋的代碼:
void undefined_behaviour_with_double_checked_locking() { if(!resource_ptr) // 1 { std::lock_guard<std::mutex> lk(resource_mutex); if(!resource_ptr) // 2 { resource_ptr.reset(new some_resource); // 3 } } resource_ptr->do_something(); // 4 }
這個模式爲何聲名狼藉呢?由於這裏有潛在的條件競爭,由於外部的讀取鎖①沒有與內部的
寫入鎖進行同步③。所以就會產生條件競爭,這個條件競爭不只覆蓋指針自己,還會影響到其
指向的對象;即便一個線程知道另外一個線程完成對指針進行寫入,它可能沒有看到新建立的
some_resource實例,而後調用do_something()④後,獲得不正確的結果。
C++標準庫提供了 std::once_flag 和 std::call_once 來處理這種狀況。比起鎖住互斥量,並顯式的檢查指
針,每一個線程只須要使用 std::call_once ,在 std::call_once 的結束時,就能安全的知道指
針已經被其餘的線程初始化了。使用 std::call_once 比顯式使用互斥量消耗的資源更少,特
別是當初始化完成後。
std::shared_ptr<some_resource> resource_ptr; std::once_flag resource_flag; // 1 void init_resource() { resource_ptr.reset(new some_resource); } void foo() { std::call_once(resource_flag,init_resource); // 能夠完整的進行一次初始化 resource_ptr->do_something(); }
2.2 保護不多更新的數據結構
雖然更新頻度很低,但更新也是有可能發生的,而且當這個可緩存被多個線程訪問,這個緩
存就須要適當的保護措施,來對其處於更新狀態時進行保護,也爲了確保線程讀到緩存中的
有效數據。
使用一
個 std::mutex 來保護數據結構,這的確有些反應過分,由於在沒有發生修改時,它將削減並
發讀取數據的可能性;這裏須要另外一種不一樣的互斥量。這種新的互斥量常被稱爲「讀者-寫者
鎖」(reader-writer mutex),由於其容許兩中不一樣的使用方式:一個「做者」線程獨佔訪問和共
享訪問,讓多個「讀者」線程併發訪問。
新的C++標準庫應該不提供這樣的互斥量,Boost庫提供了boost::shared_mutex。
3.3 嵌套鎖
C++標準庫提供了 std::recursive_mutex 類。其功能與 std::mutex 相似,除了你能夠從同一線程的單個實例上獲取多個鎖。在互斥量鎖住其餘線程前,你必須釋放你擁有的全部鎖,因此當你調用lock()三次時,你也必須調用unlock()三次。正確使用 std::lock_guard<std::recursive_mutex> 和 std::unique_lock<std::recursice_mutex> 能夠幫你處理這些問題。