std::thread詳解


1. std::thread基本介紹

   1)構造std::thread對象時,如果不帶參則會創建一個空的thread對象,但底層線程並沒有真正被創建,一般可將其他std::thread對象通過move移入其中;

      如果帶參則會創建新線程,而且會被立即運行。

   2)joinable():用於判斷std::thread對象聯結狀態,一個std::thread對象只可能處於可聯結或不可聯結兩種狀態之一。

      a. 可聯結:當線程己運行或可運行、或處於阻塞時是可聯結的。注意,如果某個底層線程已經執行完任務,但是沒有被join的話,仍然處於joinable狀態。

         即std::thread對象(對象由父線程所有)與底層線程保持着關聯時,為joinable狀態。

      b. 不可聯結:

     ① 當不帶參構造的std::thread對象為不可聯結,因為底層線程還沒創建。

     ② 己移動的std::thread對象為不可聯結。

     ③ 己調用join或detach的對象為不可聯結狀態。因為調用join()以后,底層線程己結束,而detach()會把std::thread對象和對應的底層線程之間的連接斷開。

      join():等待子線程,調用線程處於阻塞模式。join()執行完成之后,底層線程id被設置為0,即joinable()變為false。

    detach():分離子線程,與當前線程的連接被斷開,子線程成為后台線程,被C++運行時庫接管。

   3)std::thread對象析構時,會先判斷是否可joinable(),如果可聯結,則程序會直接被終止出錯。這意味着創建thread對象以后,要在隨后的某個地方調用join或

      detach以便讓std::thread處於不可聯結狀態。

   4)std::thread對象不能被復制和賦值,只能被移動。

  5)獲取當前信息

// t為std::thread對象
t.get_id();                          // 獲取線程ID
t.native_handle();                   // 返回與操作系統相關的線程句柄
std::thread::hardware_concurrency(); // 獲取CPU核數,失敗時返回0

   6)std::this_thread命名空間中相關輔助函數

get_id();                            // 獲取線程ID
yield();                             // 當前線程放棄執行,操作系統轉去調度另一線程
sleep_until(const xtime* _Abs_time); // 線程休眠至某個指定的時刻(time point),該線程才被重新喚醒
sleep_for(std::chrono::seconds(3));  // 睡眠3秒后才被重新喚醒,不過由於線程調度等原因,實際休眠時間可能比 sleep_duration 所表示的時間片更長

 

3. 傳遞參數的方式(2次傳參)

   a. 第一次傳參(向 std::thread 構造函數傳參):在創建thread對象時,std::thread構建函數中的所有參數均會按值並以副本的形式保存成一個tuple對象。

      該tuple由調用線程(一般是主線程)在堆上創建,並交由子線程管理,在子線程結束時同時被釋放。

      注:如果要達到按引用傳參的效果,可使用std::ref來傳遞。

   b. 第二次傳參(向線程函數的傳參):由於std::thread對象里保存的是參數的副本,為了效率同時兼顧一些只移動類型的對象,所有的副本均被

      std::move到線程函數,即以右值的形式傳入,所以最終傳給線程函數參數的均為右值。

   現在我們根據線程參數的類型展開討論:

   首先先給出一個用作測試的類

class Test 
{
public:
    mutable int mutableInt = 0;
    Test() : mutableInt(0) { cout << this << " " << "Test()" << endl; }
    Test(int i) : mutableInt(i) { cout << this << " " << "Test(int i)" << endl; }
    Test(const Test& w) : mutableInt(w.mutableInt) { cout << this << " " << "Test(const Test& w)" << endl; }
    Test(Test&& w)  noexcept  // 移動構造
    { 
        mutableInt = w.mutableInt; 
        cout << this << " " << "Test(Test && w)" << endl;
    }
    void func(const string& s) { cout <<"void func(string& s)" << endl; }
};

   a. 線程參數為const T&類型:這里的引用是針對於第二次傳遞的參數,也就是直接引用保存在tuple中的參數副本,而不是最原始的參數,即

      不是main中變量。但是你如果第一次向std::thread傳參時使用std::ref,首先會創建一個std::ref(它也是一個類)臨時對象,里面會保存着最原始

      變量(main中的變量)的引用,然后這個std::ref臨時對象再以副本的形式保存在std::thread中,隨后這個副本被move到線程函數。由於std::ref

      重載了類型轉換運算符operator T&(),因此會隱式轉換為Test&類型,因此起到的效果就好象main中的變量直接被按引用傳遞到線程函數中來。

      現在按照這個理解來分析下面的代碼:

      對於std::thread t1(test_ctor, w); 首先會調用一次拷貝構造,把w存儲為tuple元素;然后因為線程參數是引用,所以tuple元素給線程傳參數時不會發生

      拷貝,但實際運行結果發現多輸出了一次拷貝,這應該是std::thread隱藏的實現細節,需要閱讀源碼了。

      如果使用std::ref包裝的話,內部引用了原始的w,所以不會發生拷貝,但會發生std::ref對象的拷貝。

void test_ctor(const Test& w) { cout << &w << " " << "w.matableInt = " << ++w.mutableInt << endl; }

int main()
{
    Test w;

    // std::thread默認的按值傳參方式: 所有的實參都是被拷貝到std::thread對象的tuple中,即以副本形式被保存起來。
    // 注意,w是按值保存到std::thread中的,會調用其拷貝構造函數。外部的w沒受影響。mutableInf仍為0。
    std::thread t1(test_ctor, w);
    t1.join();
    cout << "w.mutableInt = " << w.mutableInt << endl << endl;

    // std::thread按引用傳參(std::ref), 因為w是按引用傳入到std::ref對象中的,不會調用其拷貝構造函數。
    // 由於w按引用傳遞,mutableInf被修改為1。
    std::thread t2(test_ctor, std::ref(w));
    t2.join();
    cout << "w.mutableInt = " << w.mutableInt << endl;
    return 0;
}

// 第一部分輸出如下
Test(const Test& w)  // 調用拷貝構造函數生成std::thread中的副本對象
Test(Test && w)      // std::thread中的副本移動到線程參數
w.mutableInt = 0

// 第二部分
w.mutableInt = 1

    b. 線程參數為T&類型:最終傳給線程函數參數的均為右值,而T&類型是不接受右值的,使用std::ref包裝后便不報錯了,因為它能隱式轉換成 T&。

void updateTest_ref(Test& w) { cout << &w << " " << "invoke updateTest_ref" << endl; }

int main()
{
    Test w;
    std::thread t1(updateTest_ref, w);             // 編譯失敗,因為std::thread內部是以右值形式向線程函數updateTest_ref(Test&)傳參的,
                                                   // 而右值無法用來初始化Test&引用。
    std::thread t3(updateTest_ref, std::ref(w));   // ok, 原因類似test_ctor函數中的分析。即當線程函數的形參為T&時,一般以std::ref形式傳入
    t3.join();
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM