構(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));
然后vs和g++都可以成功編譯,而且子線程可以修改外部變量的值。
當(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è)線程在管理了