C++多線程編程中通常會對共享的數據進行寫保護,以防止多線程在對共享數據成員進行讀寫時造成資源爭搶導致程序出現未定義的行為。通常的做法是在修改共享數據成員的時候進行加鎖--mutex。在使用鎖的時候通常是在對共享數據進行修改之前進行lock操作,在寫完之后再進行unlock操作,進場會出現由于疏忽導致由于lock之后在離開共享成員操作區域時忘記unlock,導致死鎖。
針對以上的問題,C++11中引入了std::unique_lock與std::lock_guard兩種數據結構。通過對lock和unlock進行一次薄的封裝,實現自動unlock的功能。
```
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();
}
```
std::unique_lock 與std::lock_guard都能實現自動加鎖與解鎖功能,但是std::unique_lock要比std::lock_guard更靈活,但是更靈活的代價是占用空間相對更大一點且相對更慢一點。
通過實現一個線程安全的隊列來說明兩者之間的差別。
```
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_;
};
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();
}
```
上面代碼只實現了關鍵的幾個函數,并使用了C++11新引入的condition_variable條件變量。從Popup與Inert兩個函數看std::unique_lock相對std::lock_guard更靈活的地方在于在等待中的線程如果在等待期間需要解鎖mutex,并在之后重新將其鎖定。而std::lock_guard卻不具備這樣的功能。
上面代碼中
cond_.wait(lk, [this]{return !Empety();});
可能會比較難以理解,
[this]{return !Empety();}
是C++11新引入的功能,lambda表達式,是一種匿名函數。方括號內表示捕獲變量。當lambda表達式返回true時(即queue不為空),wait函數會鎖定mutex。當lambda表達式返回false時,wait函數會解鎖mutex同時會將當前線程置于阻塞或等待狀態。
還存在另一種讀寫鎖,但是并沒有引入C++11,但是boost庫提供了對應的實現。讀寫鎖主要適合在于共享數據更新頻率較低,但是讀取共享數據頻率較高的場合。