[c++11]多線程編程(二)——理解線程類的構(gòu)造函數(shù)

構(gòu)造函數(shù)的參數(shù)

std::thread類的構(gòu)造函數(shù)是使用可變參數(shù)模板實(shí)現(xiàn)的,也就是說(shuō),可以傳遞任意個(gè)參數(shù),第一個(gè)參數(shù)是線程的入口函數(shù),而后面的若干個(gè)參數(shù)是該函數(shù)的參數(shù)。

第一參數(shù)的類型并不是c語(yǔ)言中的函數(shù)指針(c語(yǔ)言傳遞函數(shù)都是使用函數(shù)指針),在c++11中,增加了可調(diào)用對(duì)象(Callable Objects)的概念,總的來(lái)說(shuō),可調(diào)用對(duì)象可以是以下幾種情況:

  • 函數(shù)指針
  • 重載了operator()運(yùn)算符的類對(duì)象,即仿函數(shù)
  • lambda表達(dá)式(匿名函數(shù))
  • std::function

函數(shù)指針示例

// 普通函數(shù) 無(wú)參
void function_1() {
}

// 普通函數(shù) 1個(gè)參數(shù)
void function_2(int i) {
}

// 普通函數(shù) 2個(gè)參數(shù)
void function_3(int i, std::string m) {
}

std::thread t1(function_1);
std::thread t2(function_2, 1);
std::thread t3(function_3, 1, "hello");

t1.join();
t2.join();
t3.join();

實(shí)驗(yàn)的時(shí)候還發(fā)現(xiàn)一個(gè)問(wèn)題,如果將重載的函數(shù)作為線程的入口函數(shù),會(huì)發(fā)生編譯錯(cuò)誤!編譯器搞不清楚是哪個(gè)函數(shù),如下面的代碼:

// 普通函數(shù) 無(wú)參
void function_1() {
}

// 普通函數(shù) 1個(gè)參數(shù)
void function_1(int i) {
}
std::thread t1(function_1);
t1.join();
// 編譯錯(cuò)誤
/*
C:\Users\Administrator\Documents\untitled\main.cpp:39: 
error: no matching function for call to 'std::thread::thread(<unresolved overloaded function type>)'
     std::thread t1(function_1);
                              ^
*/

仿函數(shù)

// 仿函數(shù)
class Fctor {
public:
    // 具有一個(gè)參數(shù)
    void operator() () {

    }
};
Fctor f;
std::thread t1(f);  
// std::thread t2(Fctor()); // 編譯錯(cuò)誤 
std::thread t3((Fctor())); // ok
std::thread t4{Fctor()}; // ok

一個(gè)仿函數(shù)類生成的對(duì)象,使用起來(lái)就像一個(gè)函數(shù)一樣,比如上面的對(duì)象f,當(dāng)使用f()時(shí)就調(diào)用operator()運(yùn)算符。所以也可以讓它成為線程類的第一個(gè)參數(shù),如果這個(gè)仿函數(shù)有參數(shù),同樣的可以寫在線程類的后幾個(gè)參數(shù)上。

t2之所以編譯錯(cuò)誤,是因?yàn)榫幾g器并沒(méi)有將Fctor()解釋為一個(gè)臨時(shí)對(duì)象,而是將其解釋為一個(gè)函數(shù)聲明,編譯器認(rèn)為你聲明了一個(gè)函數(shù),這個(gè)函數(shù)不接受參數(shù),同時(shí)返回一個(gè)Factor對(duì)象。解決辦法就是在Factor()外包一層小括號(hào)(),或者在調(diào)用std::thread的構(gòu)造函數(shù)時(shí)使用{},這是c++11中的新的同意初始化語(yǔ)法。

但是,如果重載的operator()運(yùn)算符有參數(shù),就不會(huì)發(fā)生上面的錯(cuò)誤。

匿名函數(shù)

std::thread t1([](){
    std::cout << "hello" << std::endl;
});

std::thread t2([](std::string m){
    std::cout << "hello " << m << std::endl;
}, "world");

std::function

class A{
public:
    void func1(){
    }

    void func2(int i){
    }
    void func3(int i, int j){
    }
};

A a;
std::function<void(void)> f1 = std::bind(&A::func1, &a);
std::function<void(void)> f2 = std::bind(&A::func2, &a, 1);
std::function<void(int)> f3 = std::bind(&A::func2, &a, std::placeholders::_1);
std::function<void(int)> f4 = std::bind(&A::func3, &a, 1, std::placeholders::_1);
std::function<void(int, int)> f5 = std::bind(&A::func3, &a, std::placeholders::_1, std::placeholders::_2);

std::thread t1(f1);
std::thread t2(f2);
std::thread t3(f3, 1);
std::thread t4(f4, 1);
std::thread t5(f5, 1, 2);

傳值還是引用

先提出一個(gè)問(wèn)題:如果線程入口函數(shù)的的參數(shù)是引用類型,在線程內(nèi)部修改該變量,主線程的變量會(huì)改變嗎?

代碼如下:

#include <iostream>
#include <thread>
#include <string>

// 仿函數(shù)
class Fctor {
public:
    // 具有一個(gè)參數(shù) 是引用
    void operator() (std::string& msg) {
        msg = "wolrd";
    }
};



int main() {
    Fctor f;
    std::string m = "hello";
    std::thread t1(f, m);

    t1.join();
    std::cout << m << std::endl;
    return 0;
}

// vs下: 最終是:"hello"
// g++編譯器: 編譯報(bào)錯(cuò)

事實(shí)上,該代碼使用g++編譯會(huì)報(bào)錯(cuò),而使用vs2015并不會(huì)報(bào)錯(cuò),但是子線程并沒(méi)有成功改變外面的變量m。

我是這么認(rèn)為的:std::thread類,內(nèi)部也有若干個(gè)變量,當(dāng)使用構(gòu)造函數(shù)創(chuàng)建對(duì)象的時(shí)候,是將參數(shù)先賦值給這些變量,所以這些變量只是個(gè)副本,然后在線程啟動(dòng)并調(diào)用線程入口函數(shù)時(shí),傳遞的參數(shù)只是這些副本,所以內(nèi)部怎么操作都是改變副本,而不影響外面的變量。g++可能是比較嚴(yán)格,這種寫法可能會(huì)導(dǎo)致程序發(fā)生嚴(yán)重的錯(cuò)誤,索性禁止了。

而如果可以想真正傳引用,可以在調(diào)用線程類構(gòu)造函數(shù)的時(shí)候,用std::ref()包裝一下。如下面修改后的代碼:

std::thread t1(f, std::ref(m));

然后vsg++都可以成功編譯,而且子線程可以修改外部變量的值。

當(dāng)然這樣并不好,多個(gè)線程同時(shí)修改同一個(gè)變量,會(huì)發(fā)生數(shù)據(jù)競(jìng)爭(zhēng)。

同理,構(gòu)造函數(shù)的第一個(gè)參數(shù)是可調(diào)用對(duì)象,默認(rèn)情況下其實(shí)傳遞的還是一個(gè)副本。

#include <iostream>
#include <thread>
#include <string>

class A {
public:
    void f(int x, char c) {}
    int g(double x) {return 0;}
    int operator()(int N) {return 0;}
};

void foo(int x) {}

int main() {
    A a;
    std::thread t1(a, 6); // 1. 調(diào)用的是 copy_of_a()
    std::thread t2(std::ref(a), 6); // 2. a()
    std::thread t3(A(), 6); // 3. 調(diào)用的是 臨時(shí)對(duì)象 temp_a()
    std::thread t4(&A::f, a, 8, 'w'); // 4. 調(diào)用的是 copy_of_a.f()
    std::thread t5(&A::f, &a, 8, 'w'); //5.  調(diào)用的是 a.f()
    std::thread t6(std::move(a), 6); // 6. 調(diào)用的是 a.f(), a不能夠再被使用了
    t1.join();
    t2.join();
    t3.join();
    t4.join();
    t5.join();
    t6.join();
    return 0;
}

對(duì)于線程t1來(lái)說(shuō),內(nèi)部調(diào)用的線程函數(shù)其實(shí)是一個(gè)副本,所以如果在函數(shù)內(nèi)部修改了類成員,并不會(huì)影響到外面的對(duì)象。只有傳遞引用的時(shí)候才會(huì)修改。所以在這個(gè)時(shí)候就必須想清楚,到底是傳值還是傳引用!

線程對(duì)象只能移動(dòng)不可復(fù)制

線程對(duì)象之間是不能復(fù)制的,只能移動(dòng),移動(dòng)的意思是,將線程的所有權(quán)在std::thread實(shí)例間進(jìn)行轉(zhuǎn)移。

void some_function();
void some_other_function();
std::thread t1(some_function);
// std::thread t2 = t1; // 編譯錯(cuò)誤
std::thread t2 = std::move(t1); //只能移動(dòng) t1內(nèi)部已經(jīng)沒(méi)有線程了
t1 = std::thread(some_other_function); // 臨時(shí)對(duì)象賦值 默認(rèn)就是移動(dòng)操作
std::thread t3;
t3 = std::move(t2); // t2內(nèi)部已經(jīng)沒(méi)有線程了
t1 = std::move(t3); // 程序?qū)?huì)終止,因?yàn)閠1內(nèi)部已經(jīng)有一個(gè)線程在管理了

參考

  1. C++并發(fā)編程實(shí)戰(zhàn)
  2. C++ Threading #8: Using Callable Objects
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 接著上節(jié) condition_varible ,本節(jié)主要介紹future的內(nèi)容,練習(xí)代碼地址。本文參考http:/...
    jorion閱讀 15,040評(píng)論 1 5
  • 接著上節(jié) atomic,本節(jié)主要介紹condition_varible的內(nèi)容,練習(xí)代碼地址。本文參考http://...
    jorion閱讀 8,639評(píng)論 0 7
  • 接著上節(jié) mutex,本節(jié)主要介紹atomic的內(nèi)容,練習(xí)代碼地址。本文參考http://www.cplusplu...
    jorion閱讀 74,088評(píng)論 1 14
  • 前端變化有多快??jī)赡昵?,大家都用Grunt構(gòu)建,去年用Gulp + Browserify構(gòu)建,今年用Webpack...
    滾石_c2a6閱讀 686評(píng)論 0 1
  • 一切能拿錢解決的事都不是事,一切能拿錢解決的愛(ài)情都不是愛(ài)情。 “說(shuō)吧,多少錢離開我兒子?!?“阿姨,不要說(shuō)得這么明...
    也難綰系閱讀 422評(píng)論 2 3

友情鏈接更多精彩內(nèi)容