std::thread線程庫詳解(3)
目錄
前言
前兩篇的博文分別介紹了標準庫裏面的線程和鎖,這一次的博文將會介紹鎖的管理。
鎖在多線程編程中非常常用,但是一旦使用不謹慎就會導致很多問題,最常見的就是死鎖問題。
lock_guard
std::lock_guard是最常見的管理鎖的類,它會在初始化的時候自動加鎖,銷毀的時候自動解鎖,需要鎖的對象滿足BasicLockable,即存在lock和unlock方法。測試代碼:
void thread_func(int thread_id) {
{
std::lock_guard<std::mutex> guard(global_mutex);
std::cout << "Test 1:" << thread_id << std::endl;
std::this_thread::sleep_for(1s);
std::cout << "Test 2:" << thread_id << std::endl;
}
std::this_thread::sleep_for(0.5s);
std::cout << "Test 3:" << thread_id << std::endl;
}
std::lock_guard在線程一開始的代碼塊就進行了初始化,global_mutex加鎖,所以Test 1和Test 2會一起輸出,而之後代碼塊結束,std::lock_guard作為區域變量,也在此時析構釋放鎖。其輸出為

除此之外,std::lock_guard還允許輸入第二個參數std::adopt_lock_t,這個參數表明該線程已經獲取了該鎖,所以在創建對象的時候不需要再獲取鎖。
std::lock_guard<std::mutex> lk(mutex1, std::adopt_lock);
scoped_lock (C++17)
這個類和std::lock_guard類似,不過其可以同時管理多把鎖,可以同時給多把鎖加鎖。這個方法可以很好的解決哲學家就餐問題\(^1\)。
void thread_func(int thread_id, std::mutex &mutex1, std::mutex &mutex2) {
std::scoped_lock lock(mutex1, mutex2);
std::cout << "Thread " << thread_id << " is eating." << std::endl;
std::this_thread::sleep_for(1s);
std::cout << "Thread " << thread_id << " over." << std::endl;
}
std::vector<std::shared_ptr<std::thread>> philosopher;
std::vector<std::mutex> tableware_mutex(5);
for (int loop_i = 0; loop_i < 5; ++loop_i) {
philosopher.push_back(
std::make_shared<std::thread>(thread_func, loop_i, std::ref(tableware_mutex[loop_i]), std::ref(tableware_mutex[(loop_i + 1) % 5]))
);
}
for (int loop_i = 0; loop_i < 5; ++loop_i) {
philosopher.at(loop_i)->join();
}
這裡我們初始化了五個哲學家(線程)和五個餐具(鎖),每個哲學家需要兩個相鄰的餐具來進食。其結果很簡單:

可以看到,這幫哲學家們很有序的進食,沒有產生衝突。而如果我們把對應的std::scoped_lock lock(mutex1, mutex2);,改為兩個std::lock_guard,肉眼可見的會出現噁心的死鎖問題。
和std::lock_guard類似的,它也有一個參數std::adopt_lock_t,表明線程已經獲取到鎖,構造時不需要獲取鎖,不過這個參數位於第一個。
std::scoped_lock lk(std::adopt_lock, mutex1, mutex2);
unique_lock
std::unique_lock相比較與std::lock_guard更為自由,除了std::adopt_lock_t參數外,其還支持try_to_lock_t,defer_lock_t,其中try_to_lock_t為非阻塞型加鎖,defer_lock_t不在初始化的時候加鎖。
std::unique_lock<std::mutex> lk(mutex, std::adopt_lock);
std::unique_lock<std::mutex> lk(mutex, std::try_to_lock);
std::unique_lock<std::mutex> lk(mutex, std::defer_lock);
std::unique_lock支持超時加鎖:
std::unique_lock<std::timed_mutex> lk(mutex, 1s);
std::unique_lock支持移動語義,所以可以作為返回值
std::unique_lock<std::mutex> get_lock() {
std::unique_lock<std::mutex> lk(mutex);
return lk;
}
void thread_func(int thread_id) {
std::unique_lock<std::mutex> lk = get_lock();
std::cout << "Test 1: " << thread_id << std::endl;
std::this_thread::sleep_for(1s);
std::cout << "Test 2: " << thread_id << std::endl;
}
由於其允許在未加鎖構造,所以它也提供了相應的lock、try_lock、unlock等方法。
shared_lock
和std::unique_lock類似,不過這個是鎖定讀寫鎖的讀部分。
總結
本文總結了標準庫中所有的鎖管理的類,合理使用可以使代碼更優美。這是標準庫線程第三篇博文了,第四篇將會介紹線程裏面的條件變量。
ref
[1] //zh.wikipedia.org/wiki/哲學家就餐問題
博客原文://www.cnblogs.com/ink19/p/std_thread-3.html

