<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          C++ 線程的使用

          共 10909字,需瀏覽 22分鐘

           ·

          2021-09-23 07:21

          星標(biāo)/置頂 公眾號(hào)??硬核文章第一時(shí)間送達(dá)!

          C++11 之前,C++ 語言沒有對并發(fā)編程提供語言級(jí)別的支持,這使得我們在編寫可移植的并發(fā)程序時(shí),存在諸多的不便?,F(xiàn)在 C++11 中增加了線程以及線程相關(guān)的類,很方便地支持了并發(fā)編程,使得編寫的多線程程序的可移植性得到了很大的提高。

          C++11 中提供的線程類叫做 std::thread,基于這個(gè)類創(chuàng)建一個(gè)新的線程非常的簡單,只需要提供線程函數(shù)或者函數(shù)對象即可,并且可以同時(shí)指定線程函數(shù)的參數(shù)。我們首先來了解一下這個(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è)線程對象,在這個(gè)線程中不執(zhí)行任何處理動(dòng)作

          構(gòu)造函數(shù)②:移動(dòng)構(gòu)造函數(shù),將 other 的線程所有權(quán)轉(zhuǎn)移給新的 thread 對象。之后 other 不再表示執(zhí)行線程。

          構(gòu)造函數(shù)③:創(chuàng)建線程對象,并在該線程中執(zhí)行函數(shù) f 中的業(yè)務(wù)邏輯,args 是要傳遞給函數(shù) f 的參數(shù)

          任務(wù)函數(shù) f 的可選類型有很多,具體如下:

          • 普通函數(shù),類成員函數(shù),匿名函數(shù),仿函數(shù)(這些都是可調(diào)用對象類型)
          • 可以是可調(diào)用對象包裝器類型,也可以是使用綁定器綁定之后得到的類型(仿函數(shù))

          構(gòu)造函數(shù)④:使用 =delete 顯示刪除拷貝構(gòu)造,不允許線程對象之間的拷貝

          2. 公共成員函數(shù)

          2.1 get_id()

          應(yīng)用程序啟動(dòng)之后默認(rèn)只有一個(gè)線程,這個(gè)線程一般稱之為主線程或父線程,通過線程類創(chuàng)建出的線程一般稱之為子線程,每個(gè)被創(chuàng)建出的線程實(shí)例都對應(yīng)一個(gè)線程 ID,這個(gè) ID 是唯一的,可以通過這個(gè) ID 來區(qū)分和識(shí)別各個(gè)已經(jīng)存在的線程實(shí)例,這個(gè)獲取線程 ID 的函數(shù)叫做 get_id(),函數(shù)原型如下:

          std::thread::id get_id() const noexcept;

          示例程序如下:

          #include <iostream>
          #include <thread>
          #include <chrono>
          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;
          }
          1. thread t(func, 520, "i love you");:創(chuàng)建了子線程對象 t,func() 函數(shù)會(huì)在這個(gè)子線程中運(yùn)行
          • func() 是一個(gè)回調(diào)函數(shù),線程啟動(dòng)之后就會(huì)執(zhí)行這個(gè)任務(wù)函數(shù),程序猿只需要實(shí)現(xiàn)即可
          • func() 的參數(shù)是通過 thread 的參數(shù)進(jìn)行傳遞的,520,i love you 都是調(diào)用 func() 需要的實(shí)參
          • 線程類的構(gòu)造函數(shù)③ 是一個(gè)變參函數(shù),因此無需擔(dān)心線程任務(wù)函數(shù)的參數(shù)個(gè)數(shù)問題
          • 任務(wù)函數(shù) func() 一般返回值指定為 void,因?yàn)樽泳€程在調(diào)用這個(gè)函數(shù)的時(shí)候不會(huì)處理其返回值
          1. thread t1(func1);:子線程對象 t1 中的任務(wù)函數(shù)func1(),沒有參數(shù),因此在線程構(gòu)造函數(shù)中就無需指定了 通過線程對象調(diào)用 get_id() 就可以知道這個(gè)子線程的線程 ID 了,t.get_id(),t1.get_id()。
          2. 基于命名空間 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 對象)之后,在這個(gè)線程結(jié)束的時(shí)候(std::terminate ()),我們?nèi)绾稳セ厥站€程所使用的資源呢?thread 庫給我們兩種選擇:

          • 加入式(join())
          • 分離式(detach())

          另外,我們必須要在線程對象銷毀之前在二者之間作出選擇,否則程序運(yùn)行期間就會(huì)有 bug 產(chǎn)生。

          2.2 join()

          join() 字面意思是連接一個(gè)線程,意味著主動(dòng)地等待線程的終止(線程阻塞)。在某個(gè)線程中通過子線程對象調(diào)用 join() 函數(shù),調(diào)用這個(gè)函數(shù)的線程被阻塞,但是子線程對象中的任務(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ù)之后,就可以解決在上面測試程序中的 bug 了,如果要阻塞主線程的執(zhí)行,只需要在主線程中通過子線程對象調(diào)用這個(gè)方法即可,當(dāng)調(diào)用這個(gè)方法的子線程對象中的任務(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ù)子線程對象 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() 的使用,再來給大家舉一個(gè)例子,場景如下:

          程序中一共有三個(gè)線程,其中兩個(gè)子線程負(fù)責(zé)分段下載同一個(gè)文件,下載完畢之后,由主線程對這個(gè)文件進(jìn)行下一步處理,那么示例程序就應(yīng)該這么寫:

          #include <iostream>
          #include <thread>
          #include <chrono>
          using namespace std;

          void download1()
          {
              // 模擬下載, 總共耗時(shí)500ms,阻塞線程500ms
              this_thread::sleep_for(chrono::milliseconds(500));
              cout << "子線程1: " << this_thread::get_id() << ", 找到歷史正文...." << endl;
          }

          void download2()
          {
              // 模擬下載, 總共耗時(shí)300ms,阻塞線程300ms
              this_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行通過子線程對象調(diào)用了 join() 方法,這樣就能夠保證兩個(gè)子線程的任務(wù)都執(zhí)行完畢了,也就是文件內(nèi)容已經(jīng)全部下載完成,主線程再對文件進(jìn)行后續(xù)處理,如果子線程的文件沒有下載完畢,主線程就去處理文件,很顯然從邏輯上講是有問題的。

          2.3 detach()

          detach() 函數(shù)的作用是進(jìn)行線程分離,分離主線程和創(chuàng)建出的子線程。在線程分離之后,主線程退出也會(huì)一并銷毀創(chuàng)建出的所有子線程,在主線程退出之前,它可以脫離主線程繼續(xù)獨(dú)立的運(yùn)行,任務(wù)執(zhí)行完畢之后,這個(gè)子線程會(huì)自動(dòng)釋放自己占用的系統(tǒng)資源。(其實(shí)就是孩子翅膀硬了,和家里斷絕關(guān)系,自己外出闖蕩了,如果家里被誅九族還是會(huì)受牽連)。該函數(shù)函數(shù)原型如下:

          void detach();

          線程分離函數(shù)沒有參數(shù)也沒有返回值,只需要在線程成功之后,通過線程對象調(diào)用該函數(shù)即可,繼續(xù)將上面的測試程序修改一下:

          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.detach();
              t1.detach();
              // 讓主線程休眠, 等待子線程執(zhí)行完畢
              this_thread::sleep_for(chrono::seconds(5));
          }

          注意事項(xiàng):線程分離函數(shù) detach () 不會(huì)阻塞線程,子線程和主線程分離之后,在主線程中就不能再對這個(gè)子線程做任何控制了,比如:通過 join () 阻塞主線程等待子線程中的任務(wù)執(zhí)行完畢,或者調(diào)用 get_id () 獲取子線程的線程 ID。有利就有弊,魚和熊掌不可兼得,建議使用 join ()。

          2.5 joinable()

          joinable() 函數(shù)用于判斷主線程和子線程是否處理關(guān)聯(lián)(連接)狀態(tài),一般情況下,二者之間的關(guān)系處于關(guān)聯(lián)狀態(tài),該函數(shù)返回一個(gè)布爾類型:

          • 返回值為 true:主線程和子線程之間有關(guān)聯(lián)(連接)關(guān)系
          • 返回值為 false:主線程和子線程之間沒有關(guān)聯(lián)(連接)關(guān)系
          bool joinable() const noexcept;

          示例代碼如下:

          #include <iostream>
          #include <thread>
          #include <chrono>
          using namespace std;

          void foo()
          {
              this_thread::sleep_for(std::chrono::seconds(1));
          }

          int main()
          {
              thread t;
              cout << "before starting, joinable: " << t.joinable() << endl;

              t = thread(foo);
              cout << "after starting, joinable: " << t.joinable() << endl;

              t.join();
              cout << "after joining, joinable: " << t.joinable() << endl;

              thread t1(foo);
              cout << "after starting, joinable: " << t1.joinable() << endl;
              t1.detach();
              cout << "after detaching, joinable: " << t1.joinable() << endl;
          }

          示例代碼打印的結(jié)果如下:

          before starting, joinable: 0
          after starting, joinable: 1
          after joining, joinable: 0
          after starting, joinable: 1
          after detaching, joinable: 0

          基于示例代碼打印的結(jié)果可以得到以下結(jié)論:

          • 在創(chuàng)建的子線程對象的時(shí)候,如果沒有指定任務(wù)函數(shù),那么子線程不會(huì)啟動(dòng),主線程和這個(gè)子線程也不會(huì)進(jìn)行連接
          • 在創(chuàng)建的子線程對象的時(shí)候,如果指定了任務(wù)函數(shù),子線程啟動(dòng)并執(zhí)行任務(wù),主線程和這個(gè)子線程自動(dòng)連接成功
          • 子線程調(diào)用了detach()函數(shù)之后,父子線程分離,同時(shí)二者的連接斷開,調(diào)用joinable()返回false
          • 在子線程調(diào)用了join()函數(shù),子線程中的任務(wù)函數(shù)繼續(xù)執(zhí)行,直到任務(wù)處理完畢,這時(shí)join()會(huì)清理(回收)當(dāng)前子線程的相關(guān)資源,所以這個(gè)子線程和主線程的連接也就斷開了,因此,調(diào)用join()之后再調(diào)用joinable()會(huì)返回false。

          2.6 operator=

          線程中的資源是不能被復(fù)制的,因此通過 = 操作符進(jìn)行賦值操作最終并不會(huì)得到兩個(gè)完全相同的對象。

          // move (1) 
          thread& operator= (thread&& other) noexcept;
          // copy [deleted] (2) 
          thread& operator= (const other&) = delete;

          通過以上 = 操作符的重載聲明可以得知:

          • 如果 other 是一個(gè)右值,會(huì)進(jìn)行資源所有權(quán)的轉(zhuǎn)移
          • 如果 other 不是右值,禁止拷貝,該函數(shù)被顯示刪除(=delete),不可用

          3. 靜態(tài)函數(shù)

          thread 線程類還提供了一個(gè)靜態(tài)方法,用于獲取當(dāng)前計(jì)算機(jī)的 CPU 核心數(shù),根據(jù)這個(gè)結(jié)果在程序中創(chuàng)建出數(shù)量相等的線程,每個(gè)線程獨(dú)自占有一個(gè) CPU 核心,這些線程就不用分時(shí)復(fù)用 CPU 時(shí)間片,此時(shí)程序的并發(fā)效率是最高的。

          static unsigned hardware_concurrency() noexcept;

          示例代碼如下:

          #include <iostream>
          #include <thread>
          using namespace std;

          int main()
          {
              int num = thread::hardware_concurrency();
              cout << "CPU number: " << num << endl;
          }

          4. C 線程庫

          C 語言提供的線程庫不論在 window 還是 Linux 操作系統(tǒng)中都是可以使用的,看明白了這些 C 語言中的線程函數(shù)之后會(huì)發(fā)現(xiàn)它和上面的 C++ 線程類使用很類似(其實(shí)就是基于面向?qū)ο蟮乃枷脒M(jìn)行了封裝),但 C++ 的線程類用起來更簡單一些,鏈接奉上,感興趣的可以一看。

          C語言線程庫的使用

          鏈接:https://subingwen.com/cpp/thread/


          往期推薦




          專輯 | 趣味設(shè)計(jì)模式
          專輯 | 音視頻開發(fā)
          專輯 | C++ 進(jìn)階
          專輯 | 超硬核 Qt
          專輯 | 玩轉(zhuǎn) Linux
          專輯 | GitHub 開源推薦
          專輯 | 程序人生


          關(guān)注公眾號(hào)「高效程序員」??,一起優(yōu)秀!

          回復(fù) “入群” 進(jìn)技術(shù)交流群,回復(fù) “1024” 獲取海量學(xué)習(xí)資源。
          瀏覽 24
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  最近中文字幕在线中文字幕7 | 久草免费福利 | 色操屄| 婷婷去俺也去 | 豆花视频入口www |