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