線程類的構造函數

構造函數的參數

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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章