1. 程式人生 > >多執行緒傳參詳解

多執行緒傳參詳解

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