C++ 多線程(3)std::thread 詳解


@

一、頭文件

std::thread 在 頭文件中聲明,因此使用 std::thread 時需要包含 頭文件。

二、std::thread 構造函數

在這里插入圖片描述

(1). 默認構造函數,創建一個空的 thread 執行對象。
(2). 初始化構造函數,創建一個 thread對象,該 thread對象可被 joinable,新產生的線程會調用 fn 函數,該函數的參數由 args 給出。
(3). 拷貝構造函數(被禁用),意味着 thread 不可被拷貝構造。
(4). move 構造函數,move 構造函數,調用成功之后 x 不代表任何 thread 執行對象。

注意:可被 joinable 的 thread 對象必須在他們銷毀之前被主線程 join 或者將其設置為 detached.

std::thread 各種構造函數例子如下(參考):

#include <iostream>
#include <utility>
#include <thread>
#include <chrono>
 
void f1(int n)
{
    for (int i = 0; i < 5; ++i) {
        std::cout << "Thread 1 executing\n";
        ++n;
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
}
 
void f2(int& n)
{
    for (int i = 0; i < 5; ++i) {
        std::cout << "Thread 2 executing\n";
        ++n;
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
}
 
class foo
{
public:
    void bar()
    {
        for (int i = 0; i < 5; ++i) {
            std::cout << "Thread 3 executing\n";
            ++n;
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    }
    int n = 0;
};
 
class baz
{
public:
    void operator()()
    {
        for (int i = 0; i < 5; ++i) {
            std::cout << "Thread 4 executing\n";
            ++n;
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    }
    int n = 0;
};
 
int main()
{
    int n = 0;
    foo f;
    baz b;
    std::thread t1; // t1 is not a thread
    std::thread t2(f1, n + 1); // pass by value
    std::thread t3(f2, std::ref(n)); // pass by reference
    std::thread t4(std::move(t3)); // t4 is now running f2(). t3 is no longer a thread
    std::thread t5(&foo::bar, &f); // t5 runs foo::bar() on object f
    std::thread t6(b); // t6 runs baz::operator() on object b
    t2.join();
    t4.join();
    t5.join();
    t6.join();
    std::cout << "Final value of n is " << n << '\n';
    std::cout << "Final value of foo::n is " << f.n << '\n';
}

三、其他成員函數

指向當前線程std::this_thread
例如std::this_thread::get_id()

get_id 獲取線程 ID。
joinable 檢查線程是否可被 join。
join Join 線程。
detach Detach 線程
swap Swap 線程 。
native_handle 返回 native handle。
hardware_concurrency [static] 檢測硬件並發特性

四、傳遞臨時參數作為線程對象的注意事項

注意:以下問題主要都是在detach情況下發生,join下不會發生。

			   線程(函數)的傳入參數,引用&會失效,指針*還是會傳遞地址。
#include <iostream>
#include <thread>
#include <string>
using namespace std;

void myprint(const int &i, char *pmybuf){
    //i並不是mavar的引用,實際是值傳遞
    //推薦改為const int i
    cout<<i<<endl;
    //指針在detach子線程時,還是指向原來的地址。但是此時地址已經被主線程釋放會報錯
    cout<< pmybuf <<endl;
}

int main(){

    int mvar=1;
    int &mvary=mvar;
    char mybuf[]="this is a test";
    thread my_thread(myprint, mvar, mybuf);//第一個參數是函數名,后兩個參數是函數的參數
//    my_thread.join();//等待子線程執行結束
    my_thread.detach();
    cout<<"I love China"<<endl;
    return 0;
}

4.1 解決辦法:

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

void myprint(const int i, const string &pmybuf){
    //i並不是mavar的引用,實際是值傳遞
    //推薦改為const int i
    cout<<i<<endl;
    //指針在detach子線程時,還是指向原來的地址。但是此時地址已經被主線程釋放會報錯
    cout<< pmybuf <<endl;
}

int main(){

    int mvar=1;
    int &mvary=mvar;
    char mybuf[]="this is a test";
    //如果是隱式轉換,會有可能主線程執行完還沒進行轉換
//    thread my_thread(myprint, mvar, mybuf);//第一個參數是函數名,后兩個參數是函數的參數
    // 因此需要顯式的轉換,構造臨時對象
    thread my_thread(myprint, mvar, string(mybuf));//第一個參數是函數名,后兩個參數是函數的參數

//    my_thread.join();//等待子線程執行結束
    my_thread.detach();
    cout<<"I love China"<<endl;
    while(1);
    return 0;
}

4.2 原因分析

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

class A{
public:
    int m_i;
    A(int a):m_i(a){cout<<"構造函數執行"<<endl;}
    A(const A &a):m_i(a.m_i){cout<<"拷貝構造函數執行"<<endl;}
    ~A(){cout<<"析構函數調用"<<endl;}
};

void myprint(const int i, const A &pmybuf){

    cout<< &pmybuf <<endl;
}

int main(){

    int mvar=1;
    int mysec=12;
    // 如果是隱式轉換,轉換過程在子線程中執行,會有可能主線程執行完還沒進行轉換
    // 因此需要顯式的轉換,構造臨時對象
    // 在線程聲明時立刻執行構造
//    thread my_thread(myprint, mvar, mysec);//第一個參數是函數名,后兩個參數是函數的參數
    thread my_thread(myprint, mvar, A(mysec));//第一個參數是函數名,后兩個參數是函數的參數

//    my_thread.join();//等待子線程執行結束
    my_thread.detach();
    cout<<"I love China"<<endl;

    return 0;
}

4.3 總結

1、線程(函數)的傳入參數,引用&會失效,指針*還是會傳遞地址。因為主線程如果銷毀了變量內存,子線程的運行就會出錯,因此盡量不要在detach()的線程中用傳遞主線程中的指針
2、為了防止主線程先結束,detach()的線程還沒構造,調用構造的時候要顯示的調用類的拷貝構造,即為了防止主線程先結束,只有復制一份內存才行
3、想要傳遞真正的引用需要使用std::ref(param_nanm)

thread thread_obj(func,std::ref(num))

五、傳遞類對象、智能指針作為線程參數

5.1 修改子線程中的對象,不會影響主線程中的對象

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

class A {
public:
    mutable int m_i; //mutable關鍵字,任何情況下都可以修改變量。即使實在const中也可以被修改
    A(int i) :m_i(i) {}
};

void myPrint(const A& pmybuf)
{
    pmybuf.m_i = 199;

}

int main()
{
    A myObj(10);
    //myPrint(const A& pmybuf)中引用不能去掉,如果去掉會多創建一個對象
    //const也不能去掉,去掉會出錯
    //即使是傳遞的const引用,但在子線程中還是會調用拷貝構造函數構造一個新的對象,
    //所以在子線程中修改m_i的值不會影響到主線程
    //如果希望子線程中修改m_i的值影響到主線程,可以用thread myThread(myPrint, std::def(myObj));
    //這樣const就是真的引用了,myPrint定義中的const就可以去掉了,類A定義中的mutable也可以去掉了
    //此時拷貝構造也只執行一次了
    thread myThread(myPrint, myObj);
    myThread.join();
    //myThread.detach();

    cout << "Hello World!" << endl;
}

5.2 傳遞智能指針

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

void myPrint(unique_ptr<int> ptn)
{
	cout << "thread = " << std::this_thread::get_id() << endl;
}

int main()
{
	unique_ptr<int> up(new int(10));
	//獨占式指針只能通過std::move()才可以傳遞給另一個指針
	//傳遞后up就指向空,新的ptn指向原來的內存
	//所以這時就不能用detach了,因為如果主線程先執行完,ptn指向的對象就被釋放了
	thread myThread(myPrint, std::move(up));
	myThread.join();
	//myThread.detach();

	return 0;
}

參考鏈接:

https://en.cppreference.com/w/cpp/thread/thread/thread


免責聲明!

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



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