std::thread線程庫詳解(3)


目錄

前言

前兩篇的博文分別介紹了標准庫里面的線程和鎖,這一次的博文將會介紹鎖的管理。

鎖在多線程編程中非常常用,但是一旦使用不謹慎就會導致很多問題,最常見的就是死鎖問題。

lock_guard

std::lock_guard是最常見的管理鎖的類,它會在初始化的時候自動加鎖,銷毀的時候自動解鎖,需要鎖的對象滿足BasicLockable,即存在lockunlock方法。測試代碼:

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作為區域變量,也在此時析構釋放鎖。其輸出為

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();
}

這里我們初始化了五個哲學家(線程)和五個餐具(鎖),每個哲學家需要兩個相鄰的餐具來進食。其結果很簡單:

scoped_lock 輸出

可以看到,這幫哲學家們很有序的進食,沒有產生沖突。而如果我們把對應的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_tdefer_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;
}

由於其允許在未加鎖構造,所以它也提供了相應的locktry_lockunlock等方法。

shared_lock

std::unique_lock類似,不過這個是鎖定讀寫鎖的讀部分。

總結

本文總結了標准庫中所有的鎖管理的類,合理使用可以使代碼更優美。這是標准庫線程第三篇博文了,第四篇將會介紹線程里面的條件變量。

ref

[1] https://zh.wikipedia.org/wiki/哲學家就餐問題

博客原文:https://www.cnblogs.com/ink19/p/std_thread-3.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM