線程
-
std::thread
- 創建std::thread,一般會綁定一個底層的線程。若該thread還綁定好函數對象,則即刻將該函數運行於thread的底層線程。
- 線程相關的很多默認是move語義,因為在常識中線程復制是很奇怪的行為。
- joinable():是否可以阻塞至該thread綁定的底層線程運行完畢(倘若該thread沒有綁定底層線程等情況,則不可以join)
- join():本線程阻塞直至該thread的底層線程運行完畢。
- detach():該thread綁定的底層線程分離出來,任該底層線程繼續運行(thread失去對該底層線程的控制)。
互斥變量
為了避免多線程對共享變量的一段操作會發生沖突,引入了互斥體和鎖。
-
std::mutex
- 互斥體,一般搭配鎖使用,也可自己鎖住自己(lock(),unlock())。
- 若互斥體被第二個鎖請求鎖住,則第二個鎖所在線程被阻塞直至第一個鎖解鎖。
-
std::lock_guard
- 簡單鎖,構造時請求上鎖,釋放時解鎖,性能耗費較低。適用區域的多線程互斥操作。
-
std::unique_lock
- 更多功能也更靈活的鎖,隨時可解鎖或重新鎖上(減少鎖的粒度),性能耗費比前者高一點點。適用靈活的區域的多線程互斥操作。
原子變量
原子變量的意思就是單個最小的、不可分割的變量(例如一個int),原子操作則指單個極小的操作(例如一個自增操作)
C++的原子類封裝了這種數據對象,使多線程對原子變量的訪問不會造成競爭。(可以利用原子類可實現無鎖設計)
-
std::atomic_flag
- 它是一個原子的布爾類型,可支持兩種原子操作。(實際上mutex可用atomic_flag實現)
- test_and_set(): 如果atomic_flag對象被設置,則返回true; 如果atomic_flag對象未被設置,則設置之,返回false。
- clear():清除atomic_flag對象。
-
std::atomic<T>
- 對int, char, bool等基本數據類型進行原子性封裝(其實是特化模板)。
- store():修改被封裝的值。
- load(): 讀取被封裝的值。
線程獨立變量
-
thread_local
- 變量在每個線程各自獨立(類似static),並在線程結束時釋放。
條件變量
條件變量一般是用來實現多個線程的等待隊列,即主線程通知(notify)有活干了,則等待隊列中的其它線程就會被喚醒,開始干活。
-
std::condition_variable
- wait(std::unique_lock<std::mutex>& lock, Predicate pred = [](){return true;}):pred()為true時直接返回,pred()為false時,lock必須滿足已被當前線程鎖定的前提。執行原子地釋放鎖定,阻塞當前線程,並將其添加到等待*this的線程列表中。
- notify_one()/notify_all():激活某個或者所有等待的線程,被激活的線程重新獲得鎖。
虛假喚醒:
處於等待的添加變量可以通過notify_one/notify_all進行喚醒,調用函數進行信號的喚醒時,處於等待的條件變量會重新進行互斥鎖的競爭。
沒有得到互斥鎖的線程就會發生等待轉移(wait morphing),從等待信號量的隊列中轉移到等待互斥鎖的隊列中,一旦獲取到互斥鎖的所有權就會接着向下執行,
但是此時其他線程已經執行並重置了執行條件(例如一個活只需要兩個線程來干,通知完兩個線程后重置執行條件),這可能導致該線程執行引發未定義的錯誤。
//不能應對虛假喚醒 if(pred()){ cv.wait(lock); }
//利用while重復判斷執行條件,可以應對虛假喚醒 while(!pred()){ cv.wait(lock); }
//C++11提供了更方便的語法,將判斷條件作為一個參數,實際上等價於前者 cv.wait(lock,pred);
提供方
-
std::promise<T>
- 構造時,產生一個未就緒的共享狀態(包含存儲的T值和是否就緒的狀態)。可設置T值,並讓狀態變為ready。
- get_future():共享狀態綁定到future對象。
- set_value():設置共享狀態的T值,並讓狀態變為ready,則綁定的future對象可get()。
-
std::packaged_task<Func>
- 構造時綁定一個函數對象,也產生一個未就緒的共享狀態。通過thread啟動或者仿函數形式啟動該函數對象。
- 但是相比promise,沒有提供set_value()公用接口,而是當執行完綁定的函數對象,其執行結果返回值或所拋異常被存儲於能通過 std::future 對象訪問的共享狀態中。
- get_future():共享狀態綁定到future對象。
獲取方
-
std::future<T>
- 用於訪問共享狀態(即獲取值)。
- 當future的狀態還不是ready時就調用一個綁定的promise, packaged_task等的析構函數,會在期望里存儲一個異常。
- std::future有局限性,在很多線程等待時,只有一個線程能獲取等待結果。
- share():分享同一個共享狀態給另一個future
- wait():若共享狀態不是ready,則阻塞直至ready。
- get():獲得共享狀態的值,若共享狀態不是ready,則阻塞直至ready。
-
std::shared_future<T>
- 當需要多個線程等待相同的事件的結果(即多處訪問同一個共享狀態),需要用std::shared_future來替代std::future。
- shared_future與future類似,但shared_future可以拷貝、多個shared_future可以共享某個共享狀態的最終結果(即共享狀態的某個值或者異常)。
- shared_future可通過某個future對象隱式轉換,或通過future::share()顯示轉換,無論哪種轉換,被轉換的那個future對象都會變為not-valid
異步(封裝的異步操作)
-
std::async(std::launch::async | std::launch::deferred, Func, Args...)
- 異步執行一個函數,其函數執行完后的返還值綁定給使用std::async的futrue(其實是封裝了thread,packged_task的功能,使異步執行一個任務更為方便)。
- 若用創建std::thread執行異步行為,硬件底層線程可能不足,產生錯誤。而std::async將這些底層細節掩蓋住,如果使用默認參數則與標准庫的線程管理組件一起承擔線程創建和銷毀、避免過載、負責均衡的責任。
- 所以盡量使用以任務為驅動的async操作設計,而不是以線程為驅動的thread設計。
- std::async中的第一個參數是啟動策略,它控制std::async的異步行為,我們可以用三種不同的啟動策略來創建std::async:
std::launch::async參數 保證異步行為,即傳遞函數將在單獨的線程中執行。
std::launch::deferred參數 當其他線程調用get()/wait()來訪問共享狀態時,將調用非異步行為。
std::launch::async | std::launch::deferred參數 是默認行為。有了這個啟動策略,它可以異步運行或不運行,這取決於系統的負載。
///使用示例 std::future<std::string> resultFromDB = std::async(std::launch::async, fetchDataFromDB, "Data1");
std::string data1 = resultDromDB.get();