c++11 新特性實戰 (一):多執行緒操作
- 2020 年 9 月 27 日
- 筆記
- Modern c++
c++11多執行緒操作
-
執行緒
- thread
int main() { thread t1(Test1); t1.join(); thread t2(Test2); t2.join(); thread t3 = t1; thread t4(t1); thread t5 = std::move(t1); thread t6(std::move(t1)); return 0; }t3,t4創建失敗,因為thread的拷貝構造和賦值運算符重載的原型是:
thread(const thread&) = delete; thread& operator=(const thread&) = delete;被禁用了,但是t5, t6執行緒是創建成功的。std::move把t1轉換為右值,調用的是函數原型為
thread& operator=(thread&& _Other) noexcept和thread(thread&& _Other) noexcept。當執行緒對象t1被移動拷貝和移動賦值給t5和t6的時候,t1就失去了執行緒控制權,也就是一個執行緒只能同時被一個執行緒對象所控制。最直觀的是t1.joinable()返回值為false,joinable()函數後面介紹。
使用類成員函數作為執行緒參數:
class Task { public: Task(){} void Task1() {} void Task2() {} private: }; int main() { Task task; thread t3(&Task::Task1, &task); t3.join(); return 0; }關鍵點是要創建一個類對象,並作為第二個參數傳入
thread()執行緒的構造函數中去。 -
管理當前執行緒的函數
- yield
此函數的準確性為依賴於實現,特別是使用中的 OS 調度器機制和系統狀態。例如,先進先出實時調度器( Linux 的
SCHED_FIFO)將懸掛當前執行緒並將它放到準備運行的同優先順序執行緒的隊列尾(而若無其他執行緒在同優先順序,則yield無效果)。#include <iostream> #include <chrono> #include <thread> // 建議其他執行緒運行一小段時間的「忙睡眠」 void little_sleep(std::chrono::microseconds us) { auto start = std::chrono::high_resolution_clock::now(); auto end = start + us; do { std::this_thread::yield(); } while (std::chrono::high_resolution_clock::now() < end); } int main() { auto start = std::chrono::high_resolution_clock::now(); little_sleep(std::chrono::microseconds(100)); auto elapsed = std::chrono::high_resolution_clock::now() - start; std::cout << "waited for " << std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count() << " microseconds\n"; }- get_id
這個函數不用過多介紹了,就是用來獲取當前執行緒id的,用來標識執行緒的身份。
std::thread::id this_id = std::this_thread::get_id();- sleep_for
位於this_thread命名空間下,msvc下支援兩種時間參數。
std::this_thread::sleep_for(2s); std::this_thread::sleep_for(std::chrono::seconds(1));- sleep_untile
參數構建起來挺麻煩的,一般場景下要求執行緒睡眠的就用sleep_for就行了
using std::chrono::system_clock; time_t tt = system_clock::to_time_t(system_clock::now()); struct std::tm *ptm = localtime(&tt); std::this_thread::sleep_until(system_clock::from_time_t(mktime(ptm))); -
互斥
- mutex
對於互斥量看到一個很好的比喻:
單位上有一台印表機(共享數據a),你要用印表機(執行緒1要操作數據a),同事老王也要用印表機(執行緒2也要操作數據a),但是印表機同一時間只能給一個人用,此時,規定不管是誰,在用印表機之前都要向領導申請許可證(lock),用完後再向領導歸還許可證(unlock),許可證總共只有一個,沒有許可證的人就等著在用印表機的同事用完後才能申請許可證(阻塞,執行緒1lock互斥量後其他執行緒就無法lock,只能等執行緒1unlock後,其他執行緒才能lock),那麼,這個許可證就是互斥量。互斥量保證了使用印表機這一過程不被打斷。
程式碼示例:
mutex mtx; int gNum = 0; void Test1() { mtx.lock(); for(int n = 0; n < 5; ++n) gNum++; mtx.unlock(); } void Test2() { std::cout << "gNum = " << gNum << std::endl; } int main() { thread t1(Test1); t1.join(); thread t2(Test2); t2.join(); return 0; }join()表示主執行緒等待子執行緒結束再繼續執行,如果我們的期望是列印循環自增之後的gNum的值,那t1.join()就放在t2創建之前調用。因為t2的創建就標誌著t2執行緒創建好然後開始執行了。
通常mutex不單獨使用,因為lock和unlock必須配套使用,如果忘記unlock很可能造成死鎖,即使unlock寫了,但是如果在執行之前程式捕獲到異常,也還是一樣會死鎖。如何解決使用mutex造成的死鎖問題呢?下面介紹unique_gard和lock_guard的時候詳細說明。
- timed_mutex
提供互斥設施,實現有時限鎖定
- recursive_mutex
提供能被同一執行緒遞歸鎖定的互斥設施
- recursive_timed_mutex
提供能被同一執行緒遞歸鎖定的互斥設施,並實現有時限鎖定
-
通用互斥管理
- lock_guard
void Test1() { std::lock_guard<std::mutex> lg(mtx); for(int n = 0; n < 5; ++n) { gNum++; std::cout << "gNum = " << gNum << std::endl; } } int main() { thread t1(Test1); thread t2(Test1); t1.join(); t2.join(); return 0; }lock_guard相當於利用RAII機制(「資源獲取就是初始化」)把mutex封裝了一下,在構造中lock,在析構中unlock。避免了中間過程出現異常導致的mutex不能夠正常unlock.
- scoped_lock(c++17)
- unique_lock
- defer_lock_t
- try_to_lock_t
- adopt_lock_t
- defer_lock
- try_to_lock
- adopt_lock
-
通用鎖演算法
- try_lock
- lock
-
單次調用
- once_flag
- call_once
-
條件變數
- condition_variable
- condition_variable_any
- notify_all_at_thread_exit
- cv_status
-
Future
- promise
- packaged_task
- future
- shared_future
- async
- launch
- future_status
- Future錯誤
- future_error
- future_category
- future_errc

