最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C++多線程傳參的實現(xiàn)方法

 更新時間:2023年04月23日 10:07:22   作者:mingwu96  
本文主要介紹了C++多線程傳參的實現(xiàn)方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

1.線程傳參的過程

下面是thread的源代碼

template< class Function, class... Args >
explicit thread( Function&& f, Args&&... args );

源代碼很復(fù)雜,反正我是看不懂。但是有一點可以確定,默認情況下實參都是按值傳入產(chǎn)生一個副本到thread中(很多人可能都見過這句話,但可能不清楚具體細節(jié),下面舉例說明)

實參從主線程傳遞到子線程的線程函數(shù)中,需要經(jīng)過兩次傳遞。第1次發(fā)生在std::thread構(gòu)造時,實參按值傳遞并以副本形式被保存到thread的tuple中,這一過程發(fā)生在主線程。第2次發(fā)生在向線程函數(shù)傳遞時,此次傳遞是由子線程發(fā)起即發(fā)生在子線程中,并將之前std::thread內(nèi)部保存的副本以右值的形式(通過調(diào)用std::move())傳入線程函數(shù)。

1.1 內(nèi)置類型的實參

1.1.1參數(shù)按值傳遞

默認情況下,所有參數(shù)(含第1個參數(shù)可調(diào)用對象)均按值并以副本的形式保存在std::thread對象中的tuple里。

這一點的實現(xiàn)類似于std::bind(不了解bind的可以去學(xué)習(xí)一下)

void func(int& a) //左值引用
{
    a = 6;
}
 
int main()
{
    int b = 1;
    thread t1(func,b); //錯誤。對實參b按值拷貝產(chǎn)生一個副本,將該副本存放在thread的tuple,
                       //隨后對副本 調(diào)用std::move,產(chǎn)生一個右值,而func中的參數(shù)a是左值
                       //引用,不能綁定到右值
    cout << b << endl;
    t1.join();
    return 0;
}

1.1.2如果想按引用傳遞,則需要調(diào)用std::ref

void func(int& a) //左值引用
{
    a = 6;
}
 
int main()
{
    int b = 1;
    thread t1(func,std::ref(b); //std::ref傳參時,先會創(chuàng)建一個std::ref類型的臨時對象,
                           //其中保存著對b的引用。然后這個std::ref再以副本的形式保存在
                         //thread的tuple中。隨后這個副本被move到線程函數(shù),由于std::ref重載了
                        //operator T&(),因此會隱式轉(zhuǎn)換為int&類型,因此起到的效果就好象b直接
                          //被按引用傳遞到線程函數(shù)中來
 
    cout << b << endl;//b的輸出為6
    t1.join();
    return 0;
}

1.2 類類型的實參

1.2.1 傳遞的是左值對象

class A {
private:
    int m_i;
public:
    A(int i) :m_i(i) { cout << "轉(zhuǎn)換構(gòu)造" <<std::this_thread::get_id()<<endl; }
    A(const A& a):m_i(a.m_i) {cout << "拷貝構(gòu)造" <<std::this_thread::get_id()<< endl;}
     A(A&& a):m_i(a.m_i) { cout << "移動構(gòu)造" << std::this_thread::get_id()<<endl;}
    ~A() {cout << "析構(gòu)函數(shù)" <<std::this_thread::get_id()<< endl;}
};
 
void myPrint2(const A& a) 
{cout << "子線程參數(shù)地址是" <<&a<<std::this_thread::get_id()<< endl;}//4.子線程參數(shù)地址是0157D48049564
 
int main() {
    int i = 5;
    A myobj(i);//1.轉(zhuǎn)換構(gòu)造25964    6.析構(gòu)函數(shù)25964
    cout << "主線程id是" <<std::this_thread::get_id()<< endl;//2.主線程id是25964
 
   thread mytobj(myPrint2,myobj); //3.拷貝構(gòu)造25964       5.析構(gòu)函數(shù)49564
                                 //分析一下為什么上面會調(diào)用拷貝構(gòu)造
                                 //myobj是一個左值對象,因此調(diào)用拷貝構(gòu)造來生
                                 //成一個副本放入tuple中。這個過程發(fā)生在主線程中
    mytobj.join();
 
    return 0;
}

1.2.2 傳遞的是臨時對象(即右值對象)

class A {
...//定義與前面一樣
};
 
void myPrint2(const A& a) //定義與前面一樣
{...}    //4.子線程參數(shù)地址是00DED638 30492
 
int main() {
    int i = 5;
    cout << "主線程id是" <<std::this_thread::get_id()<< endl;//1.主線程id是33312
thread mytobj(myPrint2,A(i));//2.轉(zhuǎn)換構(gòu)造33312,3.移動構(gòu)造33312 
                              //4.析構(gòu)函數(shù)33312 5.析構(gòu)函數(shù)30492
                               //首先,A(i)會調(diào)用轉(zhuǎn)換構(gòu)造生成一個臨時對象
                                //隨后對這個臨時對象按值拷貝到thread中
                                // 由于臨時對象是個右值,因此調(diào)用的是移動構(gòu)造
                                //這兩個構(gòu)造都發(fā)生在主線程中
    mytobj.join();
 
    return 0;
}

關(guān)于臨時對象還有種可能

class A {
...//定義與前面一樣
};
 
void myPrint2(const A& a) //定義與前面一樣
{...}   //4.子線程參數(shù)地址是00E7D800 28216
 
int main() {
    int i = 5;
   A a(i); //1.轉(zhuǎn)換構(gòu)造41312      6.析構(gòu)函數(shù)41312
    cout << "主線程id是" <<std::this_thread::get_id()<< endl;//2.主線程id是41312
thread mytobj(myPrint2,std::move(a));//3.移動構(gòu)造41312  5.析構(gòu)函數(shù)28216
                              //4.析構(gòu)函數(shù)33312 5.析構(gòu)函數(shù)30492
                               //因為move(a)返回的是一個右值,會調(diào)用移動構(gòu)造生成到thread的 
                                 //tuple中。同樣的,這一步發(fā)生在主線程中
    mytobj.join();
 
    return 0;
}

1.2.3 傳遞的參數(shù)需要隱式類型轉(zhuǎn)換

class A {
...//定義與前面一樣
};
 
void myPrint2(const A& a) //定義與前面一樣
{...}    //3.子線程參數(shù)地址是00FFF7E4 28552
 
int main() {
    int i = 5;
    cout << "主線程id是" <<std::this_thread::get_id()<< endl;//1.主線程id是50076
thread mytobj(myPrint2,i);//2.轉(zhuǎn)換構(gòu)造28552     4.析構(gòu)函數(shù)28552
                          //分析:首先i按值傳入副本到thread,其類型仍然是int,這一步發(fā)生在主線程
                          //隨后,子線程調(diào)用move向線程函數(shù)傳參時,發(fā)生int到A的隱式類型轉(zhuǎn)換(調(diào)用 
                            /轉(zhuǎn)換構(gòu)造),這一步發(fā)生在子線程中
    mytobj.join();
 
    return 0;
}

需要說明的是,我看很多人認為如果調(diào)用detach的話,一旦主線程在子線程前面結(jié)束,那么i會被銷毀,導(dǎo)致隱式類型轉(zhuǎn)換時出錯。我覺得這是錯誤的,因為在主線程中,已經(jīng)生成了一個i的副本到thread的tuple中,就算主線程結(jié)束,i被銷毀,但i的副本不會,除非是像前面提到的const char*類型的指針,因為指針和指針的副本都指向同一個內(nèi)存塊,一旦指針指向的主線程內(nèi)存被銷毀,那么指針副本指向的就是被銷毀的內(nèi)存,導(dǎo)致野指針,

1.2.4 傳遞的參數(shù)是指針

  void func(const string& s)
 { cout <<"子線程id是 " << std::this_thread::get_id() << endl; }
 
int main(){
 
  const char* name = "Santa Claus";
  
     thread t(func, &w, name); //ok。首先name在主線程中以const char*類型作為副本被保存
                              //在thread中,當(dāng)向線程函數(shù)func傳參時,會先將之前的name副本隱式轉(zhuǎn) 
                               //換為string臨時對象再調(diào)用move傳給func的參數(shù)s
                             //同時要注意,這個隱式轉(zhuǎn)換發(fā)生在子線程調(diào)用時,即在子線程中創(chuàng)建這個臨 
                             // 時對象。這就需要確保主線程的生命周期長于子線程,否則name副本就會 
                             /變成野指針,從而無法正確構(gòu)造出string對象。
    
  
 
    //std::thread t6(&Widget::func, &w, string(name)); //為避免上述的隱式轉(zhuǎn)換可以帶來的bug。可 
                                                      //以在主線程先構(gòu)造好一個string臨時對象, 
                                                      //再傳入thread中。這樣哪怕調(diào)用的是 
                                                     //detach,子線程也很安全
 
    t.join();  //如果這里改成t.detach,并且如果主線程生命期在這行結(jié)束時(意味著主線程在子線程前面 
               //完成運行),就可能發(fā)生野指針現(xiàn)象。
}

1.3 傳入智能指針unique_ptr

智能指針其實也是個模板類,這里單獨拿出來講一下

void myPrint3(unique_ptr<A> pgn) {cout << myp.get() << endl;}//00E6BEB8
 
int main() {
 
 unique_ptr<int> myp(new int(100));
 
   thread mytobj(myPrint3,myp); //錯誤,首先unique_prt無法進行拷貝,只能移動。而myp是一個
                               //左值,不能對它進行移動構(gòu)造產(chǎn)生一個副本放入thread
   thread mytobj(myPrint3,std::move(myp));//ok,std::move(myp)返回一個右值,因此調(diào)用移動構(gòu)造產(chǎn) 
                                          //生一個副本放到thread中,這些都發(fā)生在主線程
    mytobj.join();
 
    return 0;
}

再者,討論一下上述代碼在使用detach時的情況。在此之前看下面代碼

class B {
private:
    int m_b;
public:
    B(int b) :m_b(b) { cout << "轉(zhuǎn)換構(gòu)造" << endl; }
    ~B() { cout << "析構(gòu)函數(shù)" << endl; }
};
 
void myPrint3(unique_ptr<B> pgn) { cout << pgn.get() << endl; }
 
int main() {
    unique_ptr<B> t1(new B(5));
    {
        unique_ptr<B> t2 = std::move(t1);
        cout << "時間點1" << endl;
    }
    cout << "時間點2" << endl;
    return 0;
}

輸出結(jié)果:

轉(zhuǎn)換構(gòu)造
時間點1
析構(gòu)函數(shù)
時間點2

這說明t1被銷毀時不會調(diào)用類B的析構(gòu)函數(shù),也不會釋放分配的堆區(qū)內(nèi)存。因為t1所含的指針由于后面的move操作已經(jīng)被置空了。t2退出作用域時自動銷毀,調(diào)用類的析構(gòu)函數(shù),并釋放堆區(qū)內(nèi)存

回過頭

 void myPrint3(unique_ptr<int> pgn) {cout << myp.get() << endl;}
 
int main(){
   unique_ptr<int> myp(new int(100));
 
   thread mytobj(myPrint3,std::move(myp)); 
                                         
    mytobj.detach();//即使主線程比子線程先結(jié)束,那么myp在銷毀時也不會釋放堆區(qū)內(nèi)存
                   //此時pgn包含的指針指向那塊堆區(qū)內(nèi)存。
                   //那么pgn在C++運行時庫中銷毀時,會釋放堆區(qū)內(nèi)存,不會造成內(nèi)存泄漏
                   //因此用detach也是安全的
}

到此這篇關(guān)于C++多線程傳參的實現(xiàn)方法的文章就介紹到這了,更多相關(guān)C++多線程傳參內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • C++ 靜態(tài)成員的類內(nèi)初始化詳解及實例代碼

    C++ 靜態(tài)成員的類內(nèi)初始化詳解及實例代碼

    這篇文章主要介紹了C++ 靜態(tài)成員的類內(nèi)初始化詳解及實例代碼的相關(guān)資料,需要的朋友可以參考下
    2017-02-02
  • c++之time_t和struct tm及時間戳的正確使用方式

    c++之time_t和struct tm及時間戳的正確使用方式

    C++中處理時間的常用數(shù)據(jù)類型有time_t和struct tm,time_t通常用來表示時間戳,即從1970年1月1日至今的秒數(shù),struct tm是一個結(jié)構(gòu)體,用來存儲年、月、日、時、分、秒等信息,時間戳可以通過gmtime()轉(zhuǎn)換為struct tm類型,反之亦然
    2024-10-10
  • C語言中的正則表達式使用示例詳解

    C語言中的正則表達式使用示例詳解

    正則表達式是使用單個字符串來描述、匹配一系列符合某個句法規(guī)則的字符串。本文通過示例代碼給大家介紹了C語言中的正則表達式使用,感興趣的朋友跟隨小編一起看看吧
    2019-07-07
  • C語言全局變量和局部變量的示例代碼

    C語言全局變量和局部變量的示例代碼

    本文主要介紹了C語言全局變量和局部變量的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-05-05
  • 利用C語言實現(xiàn)順序表的實例操作

    利用C語言實現(xiàn)順序表的實例操作

    順序表是線性表中的一種重要的數(shù)據(jù)結(jié)構(gòu),也是最基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu),所以他不僅是學(xué)習(xí)中的重點,也是應(yīng)用開發(fā)非常常用的一種數(shù)據(jù)結(jié)構(gòu)。這篇文章介紹如何利用C語言實現(xiàn)順序表。
    2016-08-08
  • 詳解C語言實現(xiàn)猜數(shù)字游戲

    詳解C語言實現(xiàn)猜數(shù)字游戲

    這篇文章主要為大家介紹了C語言實現(xiàn)猜數(shù)字游戲,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助<BR>
    2022-01-01
  • C語言中指針的加減運算方法示例

    C語言中指針的加減運算方法示例

    這篇文章主要給大家介紹了關(guān)于C語言中指針的加減運算的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學(xué)習(xí)或者使用C語言具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-08-08
  • Linux下用C++實現(xiàn)俄羅斯方塊

    Linux下用C++實現(xiàn)俄羅斯方塊

    這篇文章主要為大家詳細介紹了Linux下用C++實現(xiàn)俄羅斯方塊的相關(guān)資料,感興趣的小伙伴們可以參考一下
    2016-08-08
  • 詳解C++中的左值,純右值和將亡值

    詳解C++中的左值,純右值和將亡值

    C++中本身是存在左值,右值的概念,但是在C11中又出現(xiàn)了左值,純右值,將亡值得概念;這里我們主要介紹這些值的概念,感興趣的可以了解一下
    2022-09-09
  • C語言版實現(xiàn)鏈隊列

    C語言版實現(xiàn)鏈隊列

    這篇文章主要為大家詳細介紹了C語言版實現(xiàn)鏈隊列,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-07-07

最新評論

沂源县| 龙海市| 七台河市| 霍邱县| 九寨沟县| 和林格尔县| 民丰县| 泾源县| 兴山县| 霍山县| 五寨县| 祥云县| 宜章县| 昌平区| 海兴县| 武邑县| 清丰县| 潼关县| 寿光市| 宜兰县| 宣化县| 杭锦旗| 涪陵区| 惠水县| 白沙| 岳池县| 进贤县| 大邑县| 泰州市| 信宜市| 张北县| 晋宁县| 郧西县| 松潘县| 邳州市| 宝坻区| 望谟县| 永善县| 三原县| 洛隆县| 长宁县|