構造函數的參數
std::thread類的構造函數是使用可變參數模板實現的,也就是說,可以傳遞任意個參數,第一個參數是線程的入口函數,而後面的若干個參數是該函數的參數。
第一參數的類型並不是c++中的函數指針(c++傳遞函數都是使用函數指針),在c++11中,增加了可調用對象(Callable Objects)的概念,總的來說,可調用對象可以是以下幾種情況:
1.函數指針
2.重載了operator()運算符的類對象,即仿函數
3.lambda表達式(匿名函數)
4.std::function
函數指針
// 普通函數 無參
void function_1() {
}
// 普通函數 1個參數
void function_2(int i) {
}
// 普通函數 2個參數
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();
實驗的時候還發現一個問題,如果將重載的函數作爲線程的入口函數,會發生編譯錯誤!編譯器搞不清楚是哪個函數。
仿函數
class MyT
{
public:
int x;
MyT(int xx = 0) :x(xx) {}
MyT(const MyT &obj) :x(obj.x) { cout << "拷貝構造函數被調用" << endl; }
~MyT() { cout << "析構函數被調用" << endl; }
void operator()(int i)
{
cout << "子線程2運行開始" << endl;
cout << i << endl;
cout << x << endl;
cout << "子線程2運行結束" << endl;
}
};
//使用函數對象
MyT myT(6);
thread mythread2(myT,1);//值傳遞,會調用一次拷貝構造函數
mythread2.join();
lambda表達式(匿名函數)
//使用lambda表達式
auto my_lambda = [=]()->void
{
cout << "子線程3運行開始" << endl;
cout << "子線程3運行結束" << endl;
};
thread mythread3(my_lambda);
/*
thread mythread3([=]()->void
{
cout << "子線程3運行開始" << endl;
cout << "子線程3運行結束" << endl;
});
*/
mythread3.join();
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);
傳值還是引用
先提出一個問題:如果線程入口函數的的參數是引用類型,在線程內部修改該變量,主線程的變量會改變嗎?
代碼如下:
#include <iostream>
#include <thread>
#include <string>
// 仿函數
class Fctor {
public:
// 具有一個參數 是引用
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++編譯器: 編譯報錯
事實上,該代碼使用g++編譯會報錯,而使用vs2015並不會報錯,但是子線程並沒有成功改變外面的變量m。
我是這麼認爲的:std::thread類,內部也有若干個變量,當使用構造函數創建對象的時候,是將參數先賦值給這些變量,所以這些變量只是個副本,然後在線程啓動並調用線程入口函數時,傳遞的參數只是這些副本,所以內部怎麼操作都是改變副本,而不影響外面的變量。g++可能是比較嚴格,這種寫法可能會導致程序發生嚴重的錯誤,索性禁止了。
而如果可以想真正傳引用,可以在調用線程類構造函數的時候,用std::ref()包裝一下。如下面修改後的代碼:
std::thread t1(f, std::ref(m));
然後vs和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. 調用的是 copy_of_a()
std::thread t2(std::ref(a), 6); // 2. a()
std::thread t3(A(), 6); // 3. 調用的是 臨時對象 temp_a()
std::thread t4(&A::f, a, 8, 'w'); // 4. 調用的是 copy_of_a.f()
std::thread t5(&A::f, &a, 8, 'w'); //5. 調用的是 a.f()
std::thread t6(std::move(a), 6); // 6. 調用的是 a.f(), a不能夠再被使用了
t1.join();
t2.join();
t3.join();
t4.join();
t5.join();
t6.join();
return 0;
}
對於線程t1來說,內部調用的線程函數其實是一個副本,所以如果在函數內部修改了類成員,並不會影響到外面的對象。只有傳遞引用的時候纔會修改。所以在這個時候就必須想清楚,到底是傳值還是傳引用!
第一次學習的完整代碼如下:
#include "stdafx.h"
#include<iostream>
#include<map>
#include<vector>
#include<string>
#include<thread>
using namespace std;
class MyT
{
public:
int x;
MyT(int xx = 0) :x(xx) {}
MyT(const MyT &obj) :x(obj.x) { cout << "拷貝構造函數被調用" << endl; }
~MyT() { cout << "析構函數被調用" << endl; }
void operator()(int i)
{
cout << "子線程2運行開始" << endl;
cout << i << endl;
cout << x << endl;
cout << "子線程2運行結束" << endl;
}
};
void myprint()
{
cout << "子線程1運行開始" << endl;
cout << "子線程1運行結束" << endl;
}
//detach:分離,即主線程不和子線程匯合了,各執行各的,主線程不必等待子線程運行結束後再執行
//一旦detach()之後,與這個主線程關聯的thread對象就會失去與這個主線程的關聯,此時這個子線程會駐留在後臺運行
//這個子線程就相當於被運行時庫接管,當這個子線程執行結束後,由運行時庫負責清理該線程相關的資源(相當於linux的守護線程)
//一旦調用detach(),就不能再調用join()
//joinable():判斷是否可以成功使用join()或detach()
//二.其他創建線程的手法
//(2.1)用類,即使用函數對象!
//(2.2)用lambda表達式
int main()
{
thread mythread1(myprint);//(1)創建了子線程,子線程的執行起點(入口)myprint();(2)myprint線程開始執行
mythread1.join();//阻塞主線程,等待子線程執行完畢,然後主、子線程匯合
//mythread.detach();
//使用函數對象
MyT myT(6);
thread mythread2(myT,1);//值傳遞,會調用一次拷貝構造函數
mythread2.join();
//使用lambda表達式
auto my_lambda = [=]()->void
{
cout << "子線程3運行開始" << endl;
cout << "子線程3運行結束" << endl;
};
thread mythread3(my_lambda);
/*
thread mythread3([=]()->void
{
cout << "子線程3運行開始" << endl;
cout << "子線程3運行結束" << endl;
});
*/
mythread3.join();
cout << "主線程運行結束" << endl;
system("pause");
return 0;
}