C++11 std::unique_lock與std::lock_guard區別及多執行緒應用例項
阿新 • • 發佈:2019-01-04
C++多執行緒程式設計中通常會對共享的資料進行防寫,以防止多執行緒在對共享資料成員進行讀寫時造成資源爭搶導致程式出現未定義的行為。通常的做法是在修改共享資料成員的時候進行加鎖--mutex。在使用鎖的時候通常是在對共享資料進行修改之前進行lock操作,在寫完之後再進行unlock操作,進場會出現由於疏忽導致由於lock之後在離開共享成員操作區域時忘記unlock,導致死鎖。
針對以上的問題,C++11中引入了std::unique_lock與std::lock_guard兩種資料結構。通過對lock和unlock進行一次薄的封裝,實現自動unlock的功能。
std::unique_lock 與std::lock_guard都能實現自動加鎖與解鎖功能,但是std::unique_lock要比std::lock_guard更靈活,但是更靈活的代價是佔用空間相對更大一點且相對更慢一點。std::mutex mut; void insert_data() { std::lock_guard<std::mutex> lk(mut); queue.push_back(data); } void process_data() { std::unqiue_lock<std::mutex> lk(mut); queue.pop(); }
通過實現一個執行緒安全的佇列來說明兩者之間的差別。
template <typename T> class ThreadSafeQueue{ public: void Insert(T value); void Popup(T &value); bool Empety(); private: mutable std::mutex mut_; std::queue<T> que_; std::condition_variable cond_; };
上面程式碼只實現了關鍵的幾個函式,並使用了C++11新引入的condition_variable條件變數。從Popup與Inert兩個函式看std::unique_lock相對std::lock_guard更靈活的地方在於在等待中的執行緒如果在等待期間需要解鎖mutex,並在之後重新將其鎖定。而std::lock_guard卻不具備這樣的功能。template <typename T> void ThreadSafeQueue::Insert(T value){ std::lock_guard<std::mutex> lk(mut_); que_.push_back(value); cond_.notify_one(); } template <typename T> void ThreadSafeQueue::Popup(T &value){ std::unique_lock<std::mutex> lk(mut_); cond_.wait(lk, [this]{return !que_.empety();}); value = que_.front(); que_.pop(); } template <typename T> bool ThreadSafeQueue::Empty() const{ std::lock_guard<std::mutex> lk(mut_); return que_.empty(); }
上面程式碼中
cond_.wait(lk, [this]{return !Empety();});
可能會比較難以理解,
[this]{return !Empety();}
是C++11新引入的功能,lambda表示式,是一種匿名函式。方括號內表示捕獲變數。當lambda表示式返回true時(即queue不為空),wait函式會鎖定mutex。當lambda表示式返回false時,wait函式會解鎖mutex同時會將當前執行緒置於阻塞或等待狀態。
還存在另一種讀寫鎖,但是並沒有引入C++11,但是boost庫提供了對應的實現。讀寫鎖主要適合在於共享資料更新頻率較低,但是讀取共享資料頻率較高的場合。