當前位置:首頁 > 公眾號精選 > C語言與CPP編程
[導讀]c++11關于并發(fā)引入了好多好東西,這里按照如下順序介紹: std::thread相關 std::mutex相關 std::lock相關 std::atomic相關 std::call_once相關 volatile相關 std::condition_variable相關 std::future相關 async相關 std::thread相關 c++11之前你可能使用pthr


c++11關于并發(fā)引入了好多好東西,這里按照如下順序介紹:

  • std::thread相關

  • std::mutex相關

  • std::lock相關

  • std::atomic相關

  • std::call_once相關

  • volatile相關

  • std::condition_variable相關

  • std::future相關

  • async相關

std::thread相關

c++11之前你可能使用pthread_xxx來創(chuàng)建線程,繁瑣且不易讀,c++11引入了std::thread來創(chuàng)建線程,支持對線程join或者detach。直接看代碼:

#include <iostream>#include <thread>
using namespace std;
int main() { auto func = []() { for (int i = 0; i < 10; ++i) { cout << i << " "; } cout << endl; }; std::thread t(func); if (t.joinable()) { t.detach(); } auto func1 = [](int k) { for (int i = 0; i < k; ++i) { cout << i << " "; } cout << endl; }; std::thread tt(func1, 20); if (tt.joinable()) { // 檢查線程可否被join tt.join(); } return 0;}

上述代碼中,函數(shù)func和func1運行在線程對象t和tt中,從剛創(chuàng)建對象開始就會新建一個線程用于執(zhí)行函數(shù),調用join函數(shù)將會阻塞主線程,直到線程函數(shù)執(zhí)行結束,線程函數(shù)的返回值將會被忽略。如果不希望線程被阻塞執(zhí)行,可以調用線程對象的detach函數(shù),表示將線程和線程對象分離。

如果沒有調用join或者detach函數(shù),假如線程函數(shù)執(zhí)行時間較長,此時線程對象的生命周期結束調用析構函數(shù)清理資源,這時可能會發(fā)生錯誤,這里有兩種解決辦法,一個是調用join(),保證線程函數(shù)的生命周期和線程對象的生命周期相同,另一個是調用detach(),將線程和線程對象分離,這里需要注意,如果線程已經和對象分離,那我們就再也無法控制線程什么時候結束了,不能再通過join來等待線程執(zhí)行完。

這里可以對thread進行封裝,避免沒有調用join或者detach可導致程序出錯的情況出現(xiàn):

class ThreadGuard { public: enum class DesAction { join, detach };
ThreadGuard(std::thread&& t, DesAction a) : t_(std::move(t)), action_(a){};
~ThreadGuard() { if (t_.joinable()) { if (action_ == DesAction::join) { t_.join(); } else { t_.detach(); } } }
ThreadGuard(ThreadGuard&&) = default; ThreadGuard& operator=(ThreadGuard&&) = default;
std::thread& get() { return t_; }
private: std::thread t_; DesAction action_;};
int main() { ThreadGuard t(std::thread([]() { for (int i = 0; i < 10; ++i) { std::cout << "thread guard " << i << " "; } std::cout << std::endl;}), ThreadGuard::DesAction::join); return 0;}

c++11還提供了獲取線程id,或者系統(tǒng)cpu個數(shù),獲取thread native_handle,使得線程休眠等功能

std::thread t(func);cout << "當前線程ID " << t.get_id() << endl;cout << "當前cpu個數(shù) " << std::thread::hardware_concurrency() << endl;auto handle = t.native_handle();// handle可用于pthread相關操作std::this_thread::sleep_for(std::chrono::seconds(1));

std::mutex相關

std::mutex是一種線程同步的手段,用于保存多線程同時操作的共享數(shù)據(jù)。

mutex分為四種:

  • std::mutex:獨占的互斥量,不能遞歸使用,不帶超時功能

  • std::recursive_mutex:遞歸互斥量,可重入,不帶超時功能

  • std::timed_mutex:帶超時的互斥量,不能遞歸

  • std::recursive_timed_mutex:帶超時的互斥量,可以遞歸使用

拿一個std::mutex和std::timed_mutex舉例吧,別的都是類似的使用方式:

std::mutex:

#include <iostream>#include <mutex>#include <thread>
using namespace std;std::mutex mutex_;
int main() { auto func1 = [](int k) { mutex_.lock(); for (int i = 0; i < k; ++i) { cout << i << " "; } cout << endl; mutex_.unlock(); }; std::thread threads[5]; for (int i = 0; i < 5; ++i) { threads[i] = std::thread(func1, 200); } for (auto& th : threads) { th.join(); } return 0;}

std::timed_mutex:

#include <iostream>#include <mutex>#include <thread>#include <chrono>
using namespace std;std::timed_mutex timed_mutex_;
int main() { auto func1 = [](int k) { timed_mutex_.try_lock_for(std::chrono::milliseconds(200)); for (int i = 0; i < k; ++i) { cout << i << " "; } cout << endl; timed_mutex_.unlock(); }; std::thread threads[5]; for (int i = 0; i < 5; ++i) { threads[i] = std::thread(func1, 200); } for (auto& th : threads) { th.join(); } return 0;}

std::lock相關

這里主要介紹兩種RAII方式的鎖封裝,可以動態(tài)的釋放鎖資源,防止線程由于編碼失誤導致一直持有鎖。

c++11主要有std::lock_guard和std::unique_lock兩種方式,使用方式都類似,如下:

#include <iostream>#include <mutex>#include <thread>#include <chrono>
using namespace std;std::mutex mutex_;
int main() { auto func1 = [](int k) { // std::lock_guard<std::mutex> lock(mutex_); std::unique_lock<std::mutex> lock(mutex_); for (int i = 0; i < k; ++i) { cout << i << " "; } cout << endl; }; std::thread threads[5]; for (int i = 0; i < 5; ++i) { threads[i] = std::thread(func1, 200); } for (auto& th : threads) { th.join(); } return 0;}

std::lock_gurad相比于std::unique_lock更加輕量級,少了一些成員函數(shù),std::unique_lock類有unlock函數(shù),可以手動釋放鎖,所以條件變量都配合std::unique_lock使用,而不是std::lock_guard,因為條件變量在wait時需要有手動釋放鎖的能力,具體關于條件變量后面會講到。

std::atomic相關

c++11提供了原子類型std::atomic<T>,理論上這個T可以是任意類型,但是我平時只存放整形,別的還真的沒用過,整形有這種原子變量已經足夠方便,就不需要使用std::mutex來保護該變量啦??匆粋€計數(shù)器的代碼:

struct OriginCounter { // 普通的計數(shù)器 int count; std::mutex mutex_; void add() { std::lock_guard<std::mutex> lock(mutex_); ++count; }
void sub() { std::lock_guard<std::mutex> lock(mutex_); --count; }
int get() { std::lock_guard<std::mutex> lock(mutex_); return count; }};
struct NewCounter { // 使用原子變量的計數(shù)器 std::atomic<int> count; void add() { ++count; // count.store(++count);這種方式也可以 }
void sub() { --count; // count.store(--count); }
int get() { return count.load(); }};

是不是使用原子變量更加方便了呢?

std::call_once相關

c++11提供了std::call_once來保證某一函數(shù)在多線程環(huán)境中只調用一次,它需要配合std::once_flag使用,直接看使用代碼吧:

std::once_flag onceflag;
void CallOnce() { std::call_once(onceflag, []() { cout << "call once" << endl; });}
int main() { std::thread threads[5]; for (int i = 0; i < 5; ++i) { threads[i] = std::thread(CallOnce); } for (auto& th : threads) { th.join(); } return 0;}

volatile相關

貌似把volatile放在并發(fā)里介紹不太合適,但是貌似很多人都會把volatile和多線程聯(lián)系在一起,那就一起介紹下吧。

volatile通常用來建立內存屏障,volatile修飾的變量,編譯器對訪問該變量的代碼通常不再進行優(yōu)化,看下面代碼:

int *p = xxx;int a = *p;int b = *p;

a和b都等于p指向的值,一般編譯器會對此做優(yōu)化,把*p的值放入寄存器,就是傳說中的工作內存(不是主內存),之后a和b都等于寄存器的值,但是如果中間p地址的值改變,內存上的值改變啦,但a,b還是從寄存器中取的值(不一定,看編譯器優(yōu)化結果),這就不符合需求,所以在此對p加volatile修飾可以避免進行此類優(yōu)化。


注意:volatile不能解決多線程安全問題,針對特種內存才需要使用volatile,它和atomic的特點如下:
? std::atomic用于多線程訪問的數(shù)據(jù),且不用互斥量,用于并發(fā)編程中
? volatile用于讀寫操作不可以被優(yōu)化掉的內存,用于特種內存中

std::condition_variable相關

條件變量是c++11引入的一種同步機制,它可以阻塞一個線程或者個線程,直到有線程通知或者超時才會喚醒正在阻塞的線程,條件變量需要和鎖配合使用,這里的鎖就是上面介紹的std::unique_lock。

這里使用條件變量實現(xiàn)一個CountDownLatch:

class CountDownLatch { public: explicit CountDownLatch(uint32_t count) : count_(count);
void CountDown() { std::unique_lock<std::mutex> lock(mutex_); --count_; if (count_ == 0) { cv_.notify_all(); } }
void Await(uint32_t time_ms = 0) { std::unique_lock<std::mutex> lock(mutex_); while (count_ > 0) { if (time_ms > 0) { cv_.wait_for(lock, std::chrono::milliseconds(time_ms)); } else { cv_.wait(lock); } } }
uint32_t GetCount() const { std::unique_lock<std::mutex> lock(mutex_); return count_; }
private: std::condition_variable cv_; mutable std::mutex mutex_; uint32_t count_ = 0;};

關于條件變量其實還涉及到通知丟失和虛假喚醒問題,因為不是本文的主題,這里暫不介紹,大家有需要可以留言。

std::future相關

c++11關于異步操作提供了future相關的類,主要有std::future、std::promise和std::packaged_task,std::future比std::thread高級些,std::future作為異步結果的傳輸通道,通過get()可以很方便的獲取線程函數(shù)的返回值,std::promise用來包裝一個值,將數(shù)據(jù)和future綁定起來,而std::packaged_task則用來包裝一個調用對象,將函數(shù)和future綁定起來,方便異步調用。而std::future是不可以復制的,如果需要復制放到容器中可以使用std::shared_future。

std::promise與std::future配合使用

#include <functional>#include <future>#include <iostream>#include <thread>
using namespace std;
void func(std::future<int>& fut) { int x = fut.get(); cout << "value: " << x << endl;}
int main() { std::promise<int> prom; std::future<int> fut = prom.get_future(); std::thread t(func, std::ref(fut)); prom.set_value(144); t.join(); return 0;}

std::packaged_task與std::future配合使用

#include <functional>#include <future>#include <iostream>#include <thread>
using namespace std;
int func(int in) { return in + 1;}
int main() { std::packaged_task<int(int)> task(func); std::future<int> fut = task.get_future(); std::thread(std::move(task), 5).detach(); cout << "result " << fut.get() << endl; return 0;}

更多關于future的使用可以看我之前寫的關于線程池和定時器的文章。

三者之間的關系

std::future用于訪問異步操作的結果,而std::promise和std::packaged_task在future高一層,它們內部都有一個future,promise包裝的是一個值,packaged_task包裝的是一個函數(shù),當需要獲取線程中的某個值,可以使用std::promise,當需要獲取線程函數(shù)返回值,可以使用std::packaged_task。

async相關

async是比future,packaged_task,promise更高級的東西,它是基于任務的異步操作,通過async可以直接創(chuàng)建異步的任務,返回的結果會保存在future中,不需要像packaged_task和promise那么麻煩,關于線程操作應該優(yōu)先使用async,看一段使用代碼:

#include <functional>#include <future>#include <iostream>#include <thread>
using namespace std;
int func(int in) { return in + 1; }
int main() { auto res = std::async(func, 5); // res.wait(); cout << res.get() << endl; // 阻塞直到函數(shù)返回 return 0;}

使用async異步執(zhí)行函數(shù)是不是方便多啦。

async具體語法如下:

async(std::launch::async | std::launch::deferred, func, args...);

第一個參數(shù)是創(chuàng)建策略:

  • std::launch::async表示任務執(zhí)行在另一線程

  • std::launch::deferred表示延遲執(zhí)行任務,調用get或者wait時才會執(zhí)行,不會創(chuàng)建線程,惰性執(zhí)行在當前線程。

如果不明確指定創(chuàng)建策略,以上兩個都不是async的默認策略,而是未定義,它是一個基于任務的程序設計,內部有一個調度器(線程池),會根據(jù)實際情況決定采用哪種策略。

若從 std::async 獲得的 std::future 未被移動或綁定到引用,則在完整表達式結尾, std::future的析構函數(shù)將阻塞直至異步計算完成,實際上相當于同步操作:

std::async(std::launch::async, []{ f(); }); // 臨時量的析構函數(shù)等待 f()std::async(std::launch::async, []{ g(); }); // f() 完成前不開始

注意:關于async啟動策略這里網上和各種書籍介紹的五花八門,這里會以cppreference為主。

? 有時候我們如果想真正執(zhí)行異步操作可以對async進行封裝,強制使用std::launch::async策略來調用async。

template <typename F, typename... Args>inline auto ReallyAsync(F&& f, Args&&... params) { return std::async(std::launch::async, std::forward<F>(f), std::forward<Args>(params)...);}
總結





?   std::thread使線程的創(chuàng)建變得非常簡單,還可以獲取線程id等信息。

?   std::mutex通過多種方式保證了線程安全,互斥量可以獨占,也可以重入,還可以設置互斥量的超時時間,避免一直阻塞等鎖。

std::lock通過RAII技術方便了加鎖和解鎖調用,有std::lock_guard和std::unique_lock。

std::atomic提供了原子變量,更方便實現(xiàn)實現(xiàn)保護,不需要使用互斥量

std::call_once保證函數(shù)在多線程環(huán)境下只調用一次,可用于實現(xiàn)單例。

volatile常用于讀寫操作不可以被優(yōu)化掉的內存中。

std::condition_variable提供等待的同步機制,可阻塞一個或多個線程,等待其它線程通知后喚醒。

std::future用于異步調用的包裝和返回值。

async更方便的實現(xiàn)了異步調用,異步調用優(yōu)先使用async取代創(chuàng)建線程

關于c++11關于并發(fā)的新特性就介紹到這里

參考資料

https://blog.csdn.net/zhangzq86/article/details/70623394
https://zh.cppreference.com/w/cpp/atomic/atomic
https://zhuanlan.zhihu.com/p/33074506
https://www.runoob.com/w3cnote/c-volatile-keyword.html
https://zh.cppreference.com/w/cpp/thread/async
《深入應用c++11:代碼優(yōu)化與工程級應用》
《Effective Modern C++》




十大經典排序算法(動態(tài)演示+代碼)

C語言與C++面試知識總結

數(shù)據(jù)結構之堆棧

一文輕松理解內存對齊
一文輕松理解打印有效日志

一文讀懂C語言與C++動態(tài)內存

面試中常見的C語言與C++區(qū)別的問題

數(shù)據(jù)結構之線性表

免責聲明:本文內容由21ic獲得授權后發(fā)布,版權歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關機構授權發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內容真實性等。需要轉載請聯(lián)系該專欄作者,如若文章內容侵犯您的權益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或將催生出更大的獨角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉型技術解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術公司SODA.Auto推出其旗艦產品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時1.5...

關鍵字: 汽車 人工智能 智能驅動 BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務能7×24不間斷運行,同時企業(yè)卻面臨越來越多業(yè)務中斷的風險,如企業(yè)系統(tǒng)復雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務連續(xù)性,提升韌性,成...

關鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報道,騰訊和網易近期正在縮減他們對日本游戲市場的投資。

關鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產業(yè)博覽會開幕式在貴陽舉行,華為董事、質量流程IT總裁陶景文發(fā)表了演講。

關鍵字: 華為 12nm EDA 半導體

8月28日消息,在2024中國國際大數(shù)據(jù)產業(yè)博覽會上,華為常務董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權最終是由生態(tài)的繁榮決定的。

關鍵字: 華為 12nm 手機 衛(wèi)星通信

要點: 有效應對環(huán)境變化,經營業(yè)績穩(wěn)中有升 落實提質增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務引領增長 以科技創(chuàng)新為引領,提升企業(yè)核心競爭力 堅持高質量發(fā)展策略,塑強核心競爭優(yōu)勢...

關鍵字: 通信 BSP 電信運營商 數(shù)字經濟

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術學會聯(lián)合牽頭組建的NVI技術創(chuàng)新聯(lián)盟在BIRTV2024超高清全產業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現(xiàn)場 NVI技術創(chuàng)新聯(lián)...

關鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會上,軟通動力信息技術(集團)股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...

關鍵字: BSP 信息技術
關閉
關閉