<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++ 線程的使用

          共 10672字,需瀏覽 22分鐘

           ·

          2021-09-19 11:01

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

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

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

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

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

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

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

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

          2.1 get_id()

          應用程序啟動之后默認只有一個線程,這個線程一般稱之為主線程或父線程,通過線程類創(chuàng)建出的線程一般稱之為子線程,每個被創(chuàng)建出的線程實例都對應一個線程 ID,這個 ID 是唯一的,可以通過這個 ID 來區(qū)分和識別各個已經(jīng)存在的線程實例,這個獲取線程 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ù)會在這個子線程中運行
          • func() 是一個回調(diào)函數(shù),線程啟動之后就會執(zhí)行這個任務函數(shù),程序猿只需要實現(xiàn)即可
          • func() 的參數(shù)是通過 thread 的參數(shù)進行傳遞的,520,i love you 都是調(diào)用 func() 需要的實參
          • 線程類的構(gòu)造函數(shù)③ 是一個變參函數(shù),因此無需擔心線程任務函數(shù)的參數(shù)個數(shù)問題
          • 任務函數(shù) func() 一般返回值指定為 void,因為子線程在調(diào)用這個函數(shù)的時候不會處理其返回值
          1. thread t1(func1);:子線程對象 t1 中的任務函數(shù)func1(),沒有參數(shù),因此在線程構(gòu)造函數(shù)中就無需指定了 通過線程對象調(diào)用 get_id() 就可以知道這個子線程的線程 ID 了,t.get_id()t1.get_id()。
          2. 基于命名空間 this_thread 得到當前線程的線程 ID

          在上面的示例程序中有一個 bug,在主線程中依次創(chuàng)建出兩個子線程,打印兩個子線程的線程 ID,最后主線程執(zhí)行完畢就退出了(主線程就是執(zhí)行 main () 函數(shù)的那個線程)。默認情況下,主線程銷毀時會將與其關聯(lián)的兩個子線程也一并銷毀,但是這時有可能子線程中的任務還沒有執(zhí)行完畢,最后也就得不到我們想要的結(jié)果了。

          當啟動了一個線程(創(chuàng)建了一個 thread 對象)之后,在這個線程結(jié)束的時候(std::terminate ()),我們?nèi)绾稳セ厥站€程所使用的資源呢?thread 庫給我們兩種選擇:

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

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

          2.2 join()

          join() 字面意思是連接一個線程,意味著主動地等待線程的終止(線程阻塞)。在某個線程中通過子線程對象調(diào)用 join() 函數(shù),調(diào)用這個函數(shù)的線程被阻塞,但是子線程對象中的任務函數(shù)會繼續(xù)執(zhí)行,當任務執(zhí)行完畢之后 join() 會清理當前子線程中的相關資源然后返回,同時,調(diào)用該函數(shù)的線程解除阻塞繼續(xù)向下執(zhí)行。

          再次強調(diào),我們一定要搞清楚這個函數(shù)阻塞的是哪一個線程,函數(shù)在哪個線程中被執(zhí)行,那么函數(shù)就阻塞哪個線程。該函數(shù)的函數(shù)原型如下:

          void join();

          有了這樣一個線程阻塞函數(shù)之后,就可以解決在上面測試程序中的 bug 了,如果要阻塞主線程的執(zhí)行,只需要在主線程中通過子線程對象調(diào)用這個方法即可,當調(diào)用這個方法的子線程對象中的任務函數(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();
          }

          當主線程運行到第八行 t.join();,根據(jù)子線程對象 t 的任務函數(shù) func() 的執(zhí)行情況,主線程會做如下處理:

          • 如果任務函數(shù) func() 還沒執(zhí)行完畢,主線程阻塞,直到任務執(zhí)行完畢,主線程解除阻塞,繼續(xù)向下運行
          • 如果任務函數(shù) func() 已經(jīng)執(zhí)行完畢,主線程不會阻塞,繼續(xù)向下運行

          同樣,第 9 行的代碼亦如此。

          為了更好的理解 join() 的使用,再來給大家舉一個例子,場景如下:

          程序中一共有三個線程,其中兩個子線程負責分段下載同一個文件,下載完畢之后,由主線程對這個文件進行下一步處理,那么示例程序就應該這么寫:

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

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

          void download2()
          {
              // 模擬下載, 總共耗時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 << "大海賊時代再次被開啟...." << endl;
          }

          int main()
          {
              thread t1(download1);
              thread t2(download2);
              // 阻塞主線程,等待所有子線程任務執(zhí)行完畢再繼續(xù)向下執(zhí)行
              t1.join();
              t2.join();
              doSomething();
          }

          示例程序輸出的結(jié)果:

          子線程2: 72540, 找到歷史正文....
          子線程1: 79776, 找到歷史正文....
          集齊歷史正文, 呼叫羅賓....
          歷史正文解析中....
          起航,前往拉夫德爾....
          找到OnePiece, 成為海賊王, 哈哈哈!!!
          若干年后,草帽全員卒....
          大海賊時代再次被開啟....

          在上面示例程序中最核心的處理是在主線程調(diào)用 doSomething(); 之前在第 35、36行通過子線程對象調(diào)用了 join() 方法,這樣就能夠保證兩個子線程的任務都執(zhí)行完畢了,也就是文件內(nèi)容已經(jīng)全部下載完成,主線程再對文件進行后續(xù)處理,如果子線程的文件沒有下載完畢,主線程就去處理文件,很顯然從邏輯上講是有問題的。

          2.3 detach()

          detach() 函數(shù)的作用是進行線程分離,分離主線程和創(chuàng)建出的子線程。在線程分離之后,主線程退出也會一并銷毀創(chuàng)建出的所有子線程,在主線程退出之前,它可以脫離主線程繼續(xù)獨立的運行,任務執(zhí)行完畢之后,這個子線程會自動釋放自己占用的系統(tǒng)資源。(其實就是孩子翅膀硬了,和家里斷絕關系,自己外出闖蕩了,如果家里被誅九族還是會受牽連)。該函數(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));
          }

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

          2.5 joinable()

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

          • 返回值為 true:主線程和子線程之間有關聯(lián)(連接)關系
          • 返回值為 false:主線程和子線程之間沒有關聯(liá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ù),那么子線程不會啟動,主線程和這個子線程也不會進行連接
          • 在創(chuàng)建的子線程對象的時候,如果指定了任務函數(shù),子線程啟動并執(zhí)行任務,主線程和這個子線程自動連接成功
          • 子線程調(diào)用了detach()函數(shù)之后,父子線程分離,同時二者的連接斷開,調(diào)用joinable()返回false
          • 在子線程調(diào)用了join()函數(shù),子線程中的任務函數(shù)繼續(xù)執(zhí)行,直到任務處理完畢,這時join()會清理(回收)當前子線程的相關資源,所以這個子線程和主線程的連接也就斷開了,因此,調(diào)用join()之后再調(diào)用joinable()會返回false。

          2.6 operator=

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

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

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

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

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

          thread 線程類還提供了一個靜態(tài)方法,用于獲取當前計算機的 CPU 核心數(shù),根據(jù)這個結(jié)果在程序中創(chuàng)建出數(shù)量相等的線程,每個線程獨自占有一個 CPU 核心,這些線程就不用分時復用 CPU 時間片,此時程序的并發(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ù)之后會發(fā)現(xiàn)它和上面的 C++ 線程類使用很類似(其實就是基于面向?qū)ο蟮乃枷脒M行了封裝),但 C++ 的線程類用起來更簡單一些,鏈接奉上,感興趣的可以一看。

          C語言線程庫的使用

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

          瀏覽 45
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  国产熟女AV | 日韩产的人妻AV在线网 | 思思操 | 操逼做爱视 | 在线日韩欧美 |