c++11 多線程入門教程(一)

最近是恰好寫了一些c++11多線程有關(guān)的東西,就寫一下筆記留著以后自己忘記回來看吧,也不是專門寫給讀者看的,我就想到哪就寫到哪吧

看我主頁簡介免費(fèi)C++學(xué)習(xí)資源,視頻教程、職業(yè)規(guī)劃、面試詳解、學(xué)習(xí)路線、開發(fā)工具

每晚8點直播講解C++編程技術(shù)。

c++11呢,就是c++升級之后的一個版本,現(xiàn)在馬上就出c++20了,里面增加了很多對多線程支持的類,讓多線程編程更加簡單了,好了廢話不多說,先來建立一個簡單的多線程編程案例,看看c++11下多線程編程創(chuàng)建到底有多么的簡單。

1.創(chuàng)建一個簡單的多線程案例:

首先導(dǎo)入#include---用于創(chuàng)建線程?

其次導(dǎo)入#include<chrono>--用于時間延時 獲取時間之類的

定義一個線程對象t1,這就自動創(chuàng)建了一個線程,參數(shù)就是你要線程去執(zhí)行的函數(shù),t1是變量名字 隨便取?

std::thread t1(func);

下面這里返回一個毫秒級別的時間間隔參數(shù)值,間隔10毫秒?

std::chrono::milliseconds(10)

this_thread::sleep_for()就是讓此線程休眠,可以傳入休眠的時間

this_thread::sleep_for(std::chrono::milliseconds(10));讓本線程休眠10毫秒

好了知道這些參數(shù)意思就行了,看一下代碼:

#include<windows.h>#include<iostream>#include<chrono>#include<thread>usingnamespacestd;intnumber =1;intThreadProc1(){while(number <100)? ? {cout<<"thread 1 :"<< number <

join()就是阻塞線程,直到線程函數(shù)執(zhí)行完畢,如果函數(shù)有返回值,在這里會直接忽略。阻塞的目的就是讓Main主線程等待一下創(chuàng)建的線程,免得我函數(shù)還在跑,程序就直接結(jié)束了。

如果不想阻塞在這里就將join()換成使用線程的detach()方法,將線程與線程對象分離,線程就可以繼續(xù)運(yùn)行下去,并且不會造成影響。?

從示例可以看到c++11下創(chuàng)建多線程多么方便了吧 ,比在Linux下用posix創(chuàng)建還簡便,而這個也是可以在windows使用的(想想windows下多線程的代碼,看著都頭疼好吧,亂七八糟一大堆)。

2.互斥量的使用

跟往常的多線程一樣,多線程在運(yùn)行過程中都會對臨界區(qū)進(jìn)行訪問,也就是一起訪問共享資源。這樣就會造成一個問題,當(dāng)兩個線程都要對一個變量int value值假如為11,加一時,線程一取出11 進(jìn)行加一還沒有存入value,這時候線程二又取得value的11進(jìn)行加一,然后線程一存入12,線程二又存入12,這就導(dǎo)入兩個線程訪問沖突,也就是臨界區(qū)問題。所以引進(jìn)互斥量來解決。

導(dǎo)入#include <mutex>

代碼案例:

一個線程對變量number進(jìn)行加一100次,另外一個減一100次,最后結(jié)果應(yīng)該還是原來的值0。

#include<windows.h>#include<iostream>#include<chrono>#include<thread>#include<mutex>usingnamespacestd;intnumber =0;mutex g_lock;intThreadProc1(){for(inti =0; i <100; i++)? ? {? ? ? ? g_lock.lock();? ? ? ? ++number;cout<<"thread 1 :"<< number <

上面的?每次都要對mutex變量進(jìn)行鎖以及解鎖,有時候忘記解鎖就涼涼了。所以c++11還提供了一個lock_guard類,它利用了RAII機(jī)制可以保證安全釋放mutex。

在std::lock_guard對象構(gòu)造時,傳入的mutex對象(即它所管理的mutex對象)會被當(dāng)前線程鎖住。在lock_guard對象被析構(gòu)時,它所管理的mutex對象會自動解鎖,不需要程序員手動調(diào)用lock和unlock對mutex進(jìn)行上鎖和解鎖操作。lock_guard對象并不負(fù)責(zé)管理mutex對象的生命周期,lock_guard對象只是簡化了mutex對象的上鎖和解鎖操作,方便線程對互斥量上鎖,即在某個lock_guard對象的生命周期內(nèi),它所管理的鎖對象會一直保持上鎖狀態(tài);而lock_guard的生命周期結(jié)束之后,它所管理的鎖對象會被解鎖。程序員可以非常方便地使用lock_guard,而不用擔(dān)心異常安全問題。?

代碼:

#include<windows.h>#include<iostream>#include<chrono>#include<thread>#include<mutex>usingnamespacestd;intnumber =0;mutex g_lock;intThreadProc1(){? ? lock_guard loker(mutex);for(inti =0; i <100; i++)? ? {? ? ? ? ++number;cout<<"thread 1 :"<< number < loker(mutex);for(inti =0; i <100; i++)? ? {? ? ? ? --number;cout<<"thread 2 :"<< number <

除了?lock_guard,之外c++11還提供了std::unique_lock

類 unique_lock 是通用互斥包裝器,允許?延遲鎖定、鎖定的有時限嘗試、遞歸鎖定、所有權(quán)轉(zhuǎn)移和與條件變量一同使用?。?

unique_lock比lock_guard使用更加靈活,功能更加強(qiáng)大。?

使用unique_lock需要付出更多的時間、性能成本。

#include<iostream>// std::cout#include<thread>// std::thread#include<mutex>// std::mutex, std::unique_lock#include<vector>std::mutex mtx;// mutex for critical sectionstd::once_flag flag;//定義一個once_flag類型的變量作為call_once參數(shù),? ? //用std::call_once來保證多線程環(huán)境中只被調(diào)用一次voidprint_block(intn,charc){//unique_lock有多組構(gòu)造函數(shù), 這里std::defer_lock不設(shè)置鎖狀態(tài)std::unique_lock my_lock (mtx,std::defer_lock);//嘗試加鎖, 如果加鎖成功則執(zhí)行//(適合定時執(zhí)行一個job的場景, 一個線程執(zhí)行就可以, 可以用更新時間戳輔助)if(my_lock.try_lock()){for(inti=0; i ver;intnum =0;for(autoi =0; i <10; ++i){? ? ? ? ver.emplace_back(print_block,50,'*');? ? ? ? ? ver.emplace_back(run_one,std::ref(num));//emplace_back比push_back更好 是c++11增加的}for(auto&t : ver){? ? ? ? t.join();? ? }std::cout<< num <

這里還要補(bǔ)充一下跟互斥量很像的條件變量的知識。

條件變量?std::condition_variable的使用

std::condition_variable 是為了解決死鎖而生的。當(dāng)互斥操作不夠用而引入的。比如,線程可能需要等待某個條件為真才能繼續(xù)執(zhí)行,而一個忙等待循環(huán)中可能會導(dǎo)致所有其他線程都無法進(jìn)入臨界區(qū)使得條件為真時,就會發(fā)生死鎖。所以,condition_variable實例被創(chuàng)建出現(xiàn)主要就是用于喚醒等待線程從而避免死鎖。std::condition_variable的 notify_one()用于喚醒一個線程;notify_all() 則是通知所有線程。?

C++11中的std::condition_variable就像Linux下使用pthread_cond_wait和pthread_cond_signal一樣,可以讓線程休眠,直到別喚醒,現(xiàn)在在從新執(zhí)行。線程等待在多線程編程中使用非常頻繁,經(jīng)常需要等待一些異步執(zhí)行的條件的返回結(jié)果。?

示例代碼:

#include<iostream>#include<thread>#include<condition_variable>#include<mutex>#include<chrono>std::mutex g_mu;std::condition_variable g_vc;boolg_ready =false;voiddispaly_id(intid){std::unique_lock lck(g_mu);? ? g_vc.wait(lck, []() {returng_ready; });//線程阻塞,直到第二個參數(shù)返回值為真std::cout<<"id:"<< id < lck(g_mu);? ? g_ready =true;? ? g_vc.notify_all();//喚醒所有的等待線程}intmain(){std::thread t[8];for(inti =0; i <8; i++)? ? {? ? ? ? t[i] =std::thread(dispaly_id, i);? ? }std::this_thread::sleep_for(std::chrono::seconds(3));std::cout<<"all thread lock......"<

3.原子變量的使用

在新標(biāo)準(zhǔn)C++11,引入了原子操作的概念,原子操作更接近內(nèi)核,并通過這個新的頭文件提供了多種原子操作數(shù)據(jù)類型,例如,atomic_bool,atomic_int等等,如果我們在多個線程中對這些類型的共享資源進(jìn)行操作,編譯器將保證這些操作都是原子性的,也就是說,確保任意時刻只有一個線程對這個資源進(jìn)行訪問,編譯器將保證,多個線程訪問這個共享資源的正確性。從而避免了鎖的使用,提高了效率。

上面我們用互斥鎖來實現(xiàn)加一百次,減少一百次。使用原子變量會更加簡潔。?

#include<windows.h>#include<iostream>#include<chrono>#include<thread>#include<mutex>#include<atomic>usingnamespacestd;atomic number(0);//定義原子變量 一次只允許一個線程對其進(jìn)行訪問//int number = 0;//mutex g_lock;intThreadProc1(){//lock_guard<mutex> loker(mutex);for(inti =0; i <100; i++)? ? {? ? ? ? ++number;cout<<"thread 1 :"<< number <

可以看到使用了原子變量之后,代碼簡化了很多,以及以后對某些共享資源我們都可以酌情的定義為原子變量類型,很方便有木有。。。。。

4?.future與promise的使用

在c++11中增加的線程庫很方便的讓我們?nèi)ナ褂镁€程,但是因為做出了一些改變,我們并不能像往常一樣直接使用thread.join()獲取線程函數(shù)的返回值了,而我們有時候又確實要利用線程函數(shù)的返回值。?

而thread庫提供了future用來訪問異步操作的結(jié)果,因為一個異步操作的結(jié)果往往不能立即獲取,只能在未來的某個時候從某個地方獲取,這個異步操作的結(jié)果是一個未來的期待值,所以被稱為future

future和promise的作用是在不同線程之間傳遞數(shù)據(jù)。?

假設(shè)線程1需要線程2的數(shù)據(jù),那么組合使用方式如下:?

??? 線程1初始化一個promise對象和一個future對象,promise傳遞給線程2,相當(dāng)于線程2對線程1的一個承諾;future相當(dāng)于一個接受一個承諾,用來獲取未來線程2傳遞的值

??? 線程2獲取到promise后,需要對這個promise傳遞有關(guān)的數(shù)據(jù),之后線程1的future就可以獲取數(shù)據(jù)了。

??? 如果線程1想要獲取數(shù)據(jù),而線程2未給出數(shù)據(jù),則線程1阻塞,直到線程2的數(shù)據(jù)到達(dá)

示例代碼:

#include <iostream>#include <chrono>#include <thread>#include <mutex>#include <atomic>#include <future>#include <vector>void disPlay(std::future& value){? ? std::cout <<"wait some times......"<< std::endl;? ? auto result = value.get();//沒有獲取到值會阻塞等待獲取std::cout <<"Value:"<< result << std::endl;}int main(){? ? std::promise promise;? ? ? ? std::future value = promise.get_future();//將promise與future綁定std::thread t1(disPlay, std::ref(value));//創(chuàng)建線程并且函數(shù)傳參,ref()是傳一個引用std::this_thread::sleep_for(std::chrono::seconds(1));//線程延時1秒//給線程傳值進(jìn)去promise.set_value(15);? ? t1.join();? ? ? ? ? ? system("pause");return0;}

獲取future的結(jié)果有三種方式上面是get()獲取異步結(jié)果值返回,還有wait()等待異步操作完成,以及wait_for()超時等待返回結(jié)果。

5?.future與package_task的使用

std::packaged_task包裝一個可調(diào)用的對象,并且允許異步獲取該可調(diào)用對象產(chǎn)生的結(jié)果。?

std::packaged_task將其包裝的可調(diào)用對象的執(zhí)行結(jié)果傳遞給一個std::future對象,與std::promise某種程度上是很像的,promise保存一個共享狀態(tài)的值,而package_task保存的是一個函數(shù)。

示例代碼:

#include<iostream>#include<chrono>#include<thread>#include<mutex>#include<atomic>#include<future>#include<vector>inlineintfunc(intx){returnx +6;}intmain(){std::packaged_task tsk(func);std::future fut = tsk.get_future();//獲取future綁定起來std::thread(std::move(tsk),2).detach();//直接將task轉(zhuǎn)移作為線程函數(shù)使用autovalue = fut.get();std::cout<<"result:"<< value <

6?.線程異步操作函數(shù)async的用法?

,std::async比std::packaged_task,std::promise中,std::thread更高一層,它可以直接用來創(chuàng)建異步的task,異步的結(jié)果也保存在future中。完成后,外面再通過future.get/wait來獲取這個未來的結(jié)果,強(qiáng)烈推薦使用async,我們不需要關(guān)注異步任務(wù)的結(jié)果,只要等待任務(wù)完成獲取值就行了。?

現(xiàn)在來看看std::async的原型async(std::launch::async | std::launch::deferred, f, args...),第一個參數(shù)是線程的創(chuàng)建策略,有兩種策略,默認(rèn)的策略是立即創(chuàng)建線程:

std::launch::async:在調(diào)用async就開始創(chuàng)建線程。

std::launch::deferred:延遲加載方式創(chuàng)建線程。調(diào)用async時不創(chuàng)建線程,直到調(diào)用了future的get或者wait時才創(chuàng)建線程。

第二個參數(shù)是線程函數(shù),第三個參數(shù)是線程函數(shù)的參數(shù)。

代碼示例:

#include<iostream>#include<chrono>#include<thread>#include<mutex>#include<atomic>#include<future>#include<vector>intmain(){std::future fut =std::async(std::launch::async, []() {return9;? ? });std::cout<<"result:"<< fut.get() <

[]()這是c++11里面lambda表達(dá)式用法

7.std::future::wait_for()函數(shù)作用

函數(shù)原型:

template?<?class Rep,?class Period>

std::?future_status

wait_for

(?const?std::?chrono?::?duration??& timeout_duration?)?const?;

等待結(jié)果變得可用。阻塞直至經(jīng)過指定的?timeout_duration?,或結(jié)果變?yōu)榭捎茫瑑烧叩南鹊絹碚?。返回值鑒別結(jié)果的狀態(tài)。

此函數(shù)可能由于調(diào)度或資源爭議延遲而阻塞長于?timeout_duration?。

推薦標(biāo)準(zhǔn)庫用穩(wěn)定時鐘度量時長。若實現(xiàn)用系統(tǒng)時鐘代替,則等待時間可能也對時鐘調(diào)整敏感。

若調(diào)用此函數(shù)前?valid?(?)?==?false 則行為未定義。

參數(shù)

timeout_duration-要阻塞的最大時長

返回值

常量解釋

future_status::deferred要計算結(jié)果的函數(shù)仍未啟動

future_status::ready結(jié)果就緒

future_status::timeout已經(jīng)過時限

異常

時鐘、時間點或時長在執(zhí)行中可能拋的任何異常(標(biāo)準(zhǔn)庫提供的時鐘、時間點和時長決不拋出)。

注意

鼓勵實現(xiàn)在調(diào)用前檢測?valid?==?false 的情況并拋出以?future_errc?::?no_state?為 error_condition 的?future_error

代碼示例:

#include<iostream>#include<future>#include<thread>#include<chrono>intmain(){std::future future =std::async(std::launch::async, [](){std::this_thread::sleep_for(std::chrono::seconds(3));return8;? ? ? });std::cout<<"waiting...\n";std::future_status status;do{? ? ? ? status = future.wait_for(std::chrono::seconds(1));if(status ==std::future_status::deferred) {std::cout<<"deferred\n";? ? ? ? }elseif(status ==std::future_status::timeout) {std::cout<<"timeout\n";? ? ? ? }elseif(status ==std::future_status::ready) {std::cout<<"ready!\n";? ? ? ? }? ? }while(status !=std::future_status::ready);std::cout<<"result is "<< future.get() <<'\n';}

可能結(jié)果:

waiting...timeouttimeoutready!resultis8

看我主頁簡介免費(fèi)C++學(xué)習(xí)資源,視頻教程、職業(yè)規(guī)劃、面試詳解、學(xué)習(xí)路線、開發(fā)工具

每晚8點直播講解C++編程技術(shù)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 最近一直在看游雙的《高性能linux服務(wù)器編程》一書,下載鏈接: http://download.csdn.net...
    張小方閱讀 1,379評論 0 2
  • 不講語言特性,只從工程角度出發(fā),個人覺得C++標(biāo)準(zhǔn)委員會在C++11中對多線程庫的引入是有史以來做得最人道的一件事...
    stidio閱讀 14,001評論 0 11
  • <condition_variable > 頭文件主要包含了與條件變量相關(guān)的類和函數(shù)。相關(guān)的類包括 std::co...
    張霸天閱讀 3,898評論 1 0
  • 1.定義# 要求一個子系統(tǒng)(具有很多類的一個系統(tǒng))的外部與其內(nèi)部的通信必須通過一個統(tǒng)一的對象進(jìn)行。門面模式提供一個...
    tdeblog閱讀 274評論 0 0
  • A、Brief 上午體驗了一次寫字課,刀刀倒是挺喜歡的就是路程有點遠(yuǎn)。所以不會考慮。下午游泳結(jié)束后去小朋友家一起晚...
    V竇小安V閱讀 212評論 0 0

友情鏈接更多精彩內(nèi)容