C++11多線程 多線程傳參詳解

1.傳遞臨時對象做線程參數

1.1要避免的陷阱1

用detach()時,如果主線程先結束,變量就會被回收;所以用detach()的話,不推薦用引用,同時絕對不能用指針。

1.2要避免的陷阱2

只要臨時對象的用臨時構造A類對象作爲參數傳遞給線程,那麼就一定能夠在主線程結束之前,把線程函數的第二個參數構建出來,從而確保即便detach()子線程也安全運行,程序如下:

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

using namespace std;

class A
{
public:
	int m_i;
   //類型轉換構造函數,可以把一個int轉換成一個類A對象。
	A(int a):m_i(a){cout << "A::A(int a)構造函數執行!"<< endl;}
	A(const A &a) :m_i(a.m_i){cout << "A::A(A &a)複製構造函數執行!" << endl;}

	~A(){cout << "A::~A()析構函數執行!" << endl;}
};

void myprint1(const int &i, char *pmybuf)
{
	//通過查看內存可知變量mvar與它的引用mvary地址是相同的,但是傳遞到myprint()中,
	//&i的地址不是mvar的地址,所以這是個假引用,此時引用傳遞與傳值是一樣的。
	//分析可得,並不是mvar的引用,實際是值傳遞,那麼我們認爲,即便是主線程detach了子線程,
	//那麼子線程用i值任然是安全的!
	cout << i << endl;

	//第二個參數*pmybuf 是指針,*pmybuf的地址與mybuf[]相同;
	//指針在detach子線程時,絕對會有問題;
	cout << pmybuf << endl;
}

//此時用 string& 通過一個隱形轉換來接收mybuf[]的值,但此時安全嗎?
//但是mybuf是 在什麼時候轉換成string?
//事實上存在mybuf都被回收了,系統纔將mybuf去轉string的可能性
//所以此方法也是不安全的
void myprint2(const int i, const string &pmybuf)
{    
	cout << i << endl;
	cout << pmybuf << endl;
}

int main()
{
	int mvar = 1;   
	int &mvary = mvar;

	char mybuf[] = "This is a test!";

	//thread myobj(myprint1, mvar, mybuf);
	                                  
	//string(mybuf)生成臨時string對象;我們這裏直接將mybuf轉換成string對象,
	//這是一個可以保證在線程中用肯定有效的對象。
	//下個程序進行驗證
	thread myobj(myprint2, mvar, string(mybuf)); 
	myobj.detach(); 

	cout << "主線程執行!" << endl;

	system("pause");
	return 0;
}

通過自定義類的方式驗證 臨時對象可以保證主線程結束之前,把線程函數的參數構建出來

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

using namespace std;

class A
{
public:
	int m_i;
	//類型轉換構造函數,可以把一個int轉換成一個類A對象。
	A(int a) :m_i(a) { cout << "A::A(int a)構造函數執行!" << endl; }
	A(const A &a) :m_i(a.m_i) { cout << "A::A(A &a)複製構造函數執行!" << endl; }
	~A() { cout << "A::~A()析構函數執行!" << endl; }
};

void myprint(const int i, const A &pmybuf)
{
	cout << &pmybuf << endl;// 打印的是pmybuf對象的地址
}

int main()
{
	int mvar = 1;
	int mysecondpar = 12;
	//我們希望mysecondpar轉成A類型對象傳遞給myprint的第二個參數

	/*主線程什麼都不操作,快速結束主線程,運行程序後可以發現,
	沒有輸出“A::A(int a)構造函數執行!”說明主線程執行完畢,*/
	thread myobj(myprint, mvar, mysecondpar);

	/*在創建線程的同時構造臨時對象的方法傳遞參數是可行的!
	//可以保證在主線程結束之前,構造出來!*/
	thread myobj(myprint, mvar, A(mysecondpar));
	myobj.detach(); 
	
	//cout << "主線程執行!" << endl;

	return 0;
}

1.3總結

  1. 若傳遞int這種簡單類型參數,建議都是值傳遞,不要引用,防止節外生枝
  2. 如果傳遞類對象,避免隱式類型轉換。全部都在創建線程這一行就構建出臨時對象,然後在函數參數裏用引用來接;否則系統還會多構造一次對象。
  3. 終極結論:建議不使用detach(),只使用join();這樣就不存在局部變量失效導致線程對內存的非法引用問題。

2.臨時對象作爲線程參數繼續

2.1線程ID概念

Id是個數字,每一個線程(不管是主線程還是子線程)實際上都對應一個數字,而且每一個線程對應的這個數字都不同。也就是說,不同的線程,他的線程id必然不同;

線程ID可以用C++標準庫裏的函數來獲取。std::this_thread::get_id()來獲取。

2.2臨時對象構造時機抓捕

通過這個例子也可以看出,臨時對象後在main()函數中已經構造完畢了。

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

using namespace std;

class A
{
public:
	int m_i;
	//類型轉換構造函數,可以把一個int轉換成一個類A對象。
	A(int a) :m_i(a) 
	{
		cout << "A::A(int a)構造函數執行!"<<this<<"threadid:" <<std::this_thread::get_id()<< endl; 
	}
	A(const A &a) :m_i(a.m_i)
	{ 
		cout << "A::A(A &a)複製構造函數執行!" << this << "threadid:" << std::this_thread::get_id() << endl;
	}

	~A() 
	{ 
		cout << "A::~A()析構函數執行!" << this << "threadid:" << std::this_thread::get_id() << endl; 
	}
};

void myprint2(const A &pmybuf)
{
	cout << "子對象myprint的參數地址是" <<&pmybuf<<"threadid"<<std::this_thread::get_id()<<endl;// 打印的是pmybuf對象的地址
}

int main()
{
	cout << "主線程id:" << std::this_thread::get_id() <<endl;

	int  mvar = 2;
	//thread myobj(myprint2, mvar); //致命問題是在子線程中構造A類對象
	thread myobj(myprint2, A(mvar)); //用了臨時對象後,所有的A類對象都在main()函數中已經構造完畢了
	myobj.join();
	//myobj.detach(); //子線程與主線程分別執行 

	return 0;
}

3.傳遞類對象、智能指針作爲線程參數

std::ref()函數的作用 可以實現真正的引用

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

using namespace std;

class A
{
public:
	mutable int m_i;
	//類型轉換構造函數,可以把一個int轉換成一個類A對象。
	A(int a) :m_i(a)
	{
		cout << "A::A(int a)構造函數執行!" << this << "threadid:" << std::this_thread::get_id() << endl;
	}
	A(const A &a) :m_i(a.m_i)
	{
		cout << "A::A(A &a)複製構造函數執行!" << this << "threadid:" << std::this_thread::get_id() << endl;
	}

	~A()
	{
		cout << "A::~A()析構函數執行!" << this << "threadid:" << std::this_thread::get_id() << endl;
	}
};

void myprint2(const A &pmybuf)
{
	pmybuf.m_i = 199; //我們修改該值不會影響main()函數
	cout << "子對象myprint的參數地址是" << &pmybuf << "threadid" << std::this_thread::get_id() << endl;// 打印的是pmybuf對象的地址
}

int main()
{
	A myobj(10); //生成一個類對象
	thread mytobj(myprint2, std::ref(myobj)); 
	mytobj.join();

	return 0;
}
#include<iostream>
#include<thread>
#include<string>

using namespace std;

void myprint2(unique_ptr<int> pzn)
{
	;
}

int main()
{
	unique_ptr<int> myp(new int(100));
	thread mytobj(myprint2,std::move(myp));
	mytobj.join();

	return 0;
}

4. 用成員函數指針做線程函數

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

using namespace std;

class A
{
public:
	mutable int m_i;
	//類型轉換構造函數,可以把一個int轉換成一個類A對象。
	A(int a) :m_i(a)
	{
		cout << "A::A(int a)構造函數執行!" << this << "threadid:" << std::this_thread::get_id() << endl;
	}
	A(const A &a) :m_i(a.m_i)
	{
		cout << "A::A(A &a)複製構造函數執行!" << this << "threadid:" << std::this_thread::get_id() << endl;
	}

	~A()
	{
		cout << "A::~A()析構函數執行!" << this << "threadid:" << std::this_thread::get_id() << endl;
	}

	void thread_work(int num)
	{
		cout << "子線程thread——work執行!" << this << "threadid:" << std::this_thread::get_id() << endl;
	}
};

int main()
{
	A myobj(10);
	thread mytobj(&A::thread_work, &myobj, 15); 
	mytobj.join();
	
	return 0;
}

注:該文是C++11併發多線程視頻教程筆記,詳情學習:https://study.163.com/course/courseMain.htm?courseId=1006067356

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章