C 线程的使用~(上)
C 11 之前,C 語(yǔ)言沒有對(duì)并發(fā)編程提供語(yǔ)言級(jí)別的支持,這使得我們?cè)诰帉懣梢浦驳牟l(fā)程序時(shí),存在諸多的不便。現(xiàn)在 C 11 中增加了線程以及線程相關(guān)的類,很方便地支持了并發(fā)編程,使得編寫的多線程程序的可移植性得到了很大的提高。
C 11 中提供的線程類叫做?std::thread,基于這個(gè)類創(chuàng)建一個(gè)新的線程非常的簡(jiǎn)單,只需要提供線程函數(shù)或者函數(shù)對(duì)象即可,并且可以同時(shí)指定線程函數(shù)的參數(shù)。我們首先來(lái)了解一下這個(gè)類提供的一些常用 API:
1. 構(gòu)造函數(shù)
//?① thread()?noexcept; //?② thread(?thread&&?other?)?noexcept; //?③ template<?class?function,?class...?args?> explicit?thread(?Function&&?f,?Args&&...?args?); //?④ thread(?const?thread&?)?=?delete;構(gòu)造函數(shù)①:默認(rèn)構(gòu)造函,構(gòu)造一個(gè)線程對(duì)象,在這個(gè)線程中不執(zhí)行任何處理動(dòng)作
構(gòu)造函數(shù)②:移動(dòng)構(gòu)造函數(shù),將 other 的線程所有權(quán)轉(zhuǎn)移給新的 thread 對(duì)象。之后 other 不再表示執(zhí)行線程。
構(gòu)造函數(shù)③:創(chuàng)建線程對(duì)象,并在該線程中執(zhí)行函數(shù) f 中的業(yè)務(wù)邏輯,args 是要傳遞給函數(shù) f 的參數(shù)
任務(wù)函數(shù) f 的可選類型有很多,具體如下:
普通函數(shù),類成員函數(shù),匿名函數(shù),仿函數(shù)(這些都是可調(diào)用對(duì)象類型)
可以是可調(diào)用對(duì)象包裝器類型,也可以是使用綁定器綁定之后得到的類型(仿函數(shù))
構(gòu)造函數(shù)④:使用 =delete 顯示刪除拷貝構(gòu)造,不允許線程對(duì)象之間的拷貝
2. 公共成員函數(shù)
2.1 get_id()
應(yīng)用程序啟動(dòng)之后默認(rèn)只有一個(gè)線程,這個(gè)線程一般稱之為主線程或父線程,通過(guò)線程類創(chuàng)建出的線程一般稱之為子線程,每個(gè)被創(chuàng)建出的線程實(shí)例都對(duì)應(yīng)一個(gè)線程 ID,這個(gè) ID 是唯一的,可以通過(guò)這個(gè) ID 來(lái)區(qū)分和識(shí)別各個(gè)已經(jīng)存在的線程實(shí)例,這個(gè)獲取線程 ID 的函數(shù)叫做?get_id(),函數(shù)原型如下:
std::thread::id?get_id()?const?noexcept;示例程序如下:
#include? #include? #include? using?namespace?std;void?func(int?num,?string?str) {for?(int?i?=?0;?i?<?10;? i){cout?<<?"子線程:?i?=?"?<<?i?<<?"num:?"?<<?num?<<?",?str:?"?<<?str?<<?endl;} }void?func1() {for?(int?i?=?0;?i?<?10;? i){cout?<<?"子線程:?i?=?"?<<?i?<<?endl;} }int?main() {cout?<<?"主線程的線程ID:?"?<<?this_thread::get_id()?<<?endl;thread?t(func,?520,?"i?love?you");thread?t1(func1);cout?<<?"線程t?的線程ID:?"?<<?t.get_id()?<<?endl;cout?<<?"線程t1的線程ID:?"?<<?t1.get_id()?<<?endl; }thread t(func, 520, "i love you");:創(chuàng)建了子線程對(duì)象 t,func() 函數(shù)會(huì)在這個(gè)子線程中運(yùn)行
func()?是一個(gè)回調(diào)函數(shù),線程啟動(dòng)之后就會(huì)執(zhí)行這個(gè)任務(wù)函數(shù),程序猿只需要實(shí)現(xiàn)即可
func()?的參數(shù)是通過(guò) thread 的參數(shù)進(jìn)行傳遞的,520,i love you?都是調(diào)用?func()?需要的實(shí)參
線程類的構(gòu)造函數(shù)③ 是一個(gè)變參函數(shù),因此無(wú)需擔(dān)心線程任務(wù)函數(shù)的參數(shù)個(gè)數(shù)問(wèn)題
任務(wù)函數(shù)?func()?一般返回值指定為?void,因?yàn)樽泳€程在調(diào)用這個(gè)函數(shù)的時(shí)候不會(huì)處理其返回值
thread t1(func1);:子線程對(duì)象 t1 中的任務(wù)函數(shù)func1(),沒有參數(shù),因此在線程構(gòu)造函數(shù)中就無(wú)需指定了 通過(guò)線程對(duì)象調(diào)用?get_id()?就可以知道這個(gè)子線程的線程 ID 了,t.get_id(),t1.get_id()。
基于命名空間?this_thread?得到當(dāng)前線程的線程 ID
在上面的示例程序中有一個(gè) bug,在主線程中依次創(chuàng)建出兩個(gè)子線程,打印兩個(gè)子線程的線程 ID,最后主線程執(zhí)行完畢就退出了(主線程就是執(zhí)行?main ()?函數(shù)的那個(gè)線程)。默認(rèn)情況下,主線程銷毀時(shí)會(huì)將與其關(guān)聯(lián)的兩個(gè)子線程也一并銷毀,但是這時(shí)有可能子線程中的任務(wù)還沒有執(zhí)行完畢,最后也就得不到我們想要的結(jié)果了。
當(dāng)啟動(dòng)了一個(gè)線程(創(chuàng)建了一個(gè) thread 對(duì)象)之后,在這個(gè)線程結(jié)束的時(shí)候(std::terminate ()),我們?nèi)绾稳セ厥站€程所使用的資源呢?thread 庫(kù)給我們兩種選擇:
加入式(join())
分離式(detach())
另外,我們必須要在線程對(duì)象銷毀之前在二者之間作出選擇,否則程序運(yùn)行期間就會(huì)有 bug 產(chǎn)生。
2.2 join()
join()?字面意思是連接一個(gè)線程,意味著主動(dòng)地等待線程的終止(線程阻塞)。在某個(gè)線程中通過(guò)子線程對(duì)象調(diào)用?join()?函數(shù),調(diào)用這個(gè)函數(shù)的線程被阻塞,但是子線程對(duì)象中的任務(wù)函數(shù)會(huì)繼續(xù)執(zhí)行,當(dāng)任務(wù)執(zhí)行完畢之后?join()?會(huì)清理當(dāng)前子線程中的相關(guān)資源然后返回,同時(shí),調(diào)用該函數(shù)的線程解除阻塞繼續(xù)向下執(zhí)行。
再次強(qiáng)調(diào),我們一定要搞清楚這個(gè)函數(shù)阻塞的是哪一個(gè)線程,函數(shù)在哪個(gè)線程中被執(zhí)行,那么函數(shù)就阻塞哪個(gè)線程。該函數(shù)的函數(shù)原型如下:
void?join();有了這樣一個(gè)線程阻塞函數(shù)之后,就可以解決在上面測(cè)試程序中的 bug 了,如果要阻塞主線程的執(zhí)行,只需要在主線程中通過(guò)子線程對(duì)象調(diào)用這個(gè)方法即可,當(dāng)調(diào)用這個(gè)方法的子線程對(duì)象中的任務(wù)函數(shù)執(zhí)行完畢之后,主線程的阻塞也就隨之解除了。修改之后的示例代碼如下:
int?main() {cout?<<?"主線程的線程ID:?"?<<?this_thread::get_id()?<<?endl;thread?t(func,?520,?"i?love?you");thread?t1(func1);cout?<<?"線程t?的線程ID:?"?<<?t.get_id()?<<?endl;cout?<<?"線程t1的線程ID:?"?<<?t1.get_id()?<<?endl;t.join();t1.join(); }當(dāng)主線程運(yùn)行到第八行?t.join();,根據(jù)子線程對(duì)象 t 的任務(wù)函數(shù)?func()?的執(zhí)行情況,主線程會(huì)做如下處理:
如果任務(wù)函數(shù)?func()?還沒執(zhí)行完畢,主線程阻塞,直到任務(wù)執(zhí)行完畢,主線程解除阻塞,繼續(xù)向下運(yùn)行
如果任務(wù)函數(shù)?func()?已經(jīng)執(zhí)行完畢,主線程不會(huì)阻塞,繼續(xù)向下運(yùn)行
同樣,第 9 行的代碼亦如此。
為了更好的理解?join()?的使用,再來(lái)給大家舉一個(gè)例子,場(chǎng)景如下:
程序中一共有三個(gè)線程,其中兩個(gè)子線程負(fù)責(zé)分段下載同一個(gè)文件,下載完畢之后,由主線程對(duì)這個(gè)文件進(jìn)行下一步處理,那么示例程序就應(yīng)該這么寫:
#include? #include? #include? using?namespace?std;void?download1() {//?模擬下載,?總共耗時(shí)500ms,阻塞線程500msthis_thread::sleep_for(chrono::milliseconds(500));cout?<<?"子線程1:?"?<<?this_thread::get_id()?<<?",?找到歷史正文...."?<<?endl; }void?download2() {//?模擬下載,?總共耗時(shí)300ms,阻塞線程300msthis_thread::sleep_for(chrono::milliseconds(300));cout?<<?"子線程2:?"?<<?this_thread::get_id()?<<?",?找到歷史正文...."?<<?endl; }void?doSomething() {cout?<<?"集齊歷史正文,?呼叫羅賓...."?<<?endl;cout?<<?"歷史正文解析中...."?<<?endl;cout?<<?"起航,前往拉夫德爾...."?<<?endl;cout?<<?"找到OnePiece,?成為海賊王,?哈哈哈!!!"?<<?endl;cout?<<?"若干年后,草帽全員卒...."?<<?endl;cout?<<?"大海賊時(shí)代再次被開啟...."?<<?endl; }int?main() {thread?t1(download1);thread?t2(download2);//?阻塞主線程,等待所有子線程任務(wù)執(zhí)行完畢再繼續(xù)向下執(zhí)行t1.join();t2.join();doSomething(); }示例程序輸出的結(jié)果:
子線程2:?72540,?找到歷史正文.... 子線程1:?79776,?找到歷史正文.... 集齊歷史正文,?呼叫羅賓.... 歷史正文解析中.... 起航,前往拉夫德爾.... 找到OnePiece,?成為海賊王,?哈哈哈!!! 若干年后,草帽全員卒.... 大海賊時(shí)代再次被開啟....在上面示例程序中最核心的處理是在主線程調(diào)用?doSomething(); 之前在第 35、36行通過(guò)子線程對(duì)象調(diào)用了?join()?方法,這樣就能夠保證兩個(gè)子線程的任務(wù)都執(zhí)行完畢了,也就是文件內(nèi)容已經(jīng)全部下載完成,主線程再對(duì)文件進(jìn)行后續(xù)處理,如果子線程的文件沒有下載完畢,主線程就去處理文件,很顯然從邏輯上講是有問(wèn)題的。
聲明:
本文于網(wǎng)絡(luò)整理,版權(quán)歸原作者所有,如來(lái)源信息有誤或侵犯權(quán)益,請(qǐng)聯(lián)系我們刪除或授權(quán)事宜。
總結(jié)
以上是生活随笔為你收集整理的C 线程的使用~(上)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 分享10个适合初学者学习的C开源项目代码
- 下一篇: C语言中的“三字母词”坑了工程师