天天看點

C++11多線程注意事項以及detach中的坑

多線程程式設計是必須要掌握的,以前多線程基本是靠系統API或者第三方庫完成的,比如windows的API函數CreateThread,linux建立線程函數pthread_create,但是這樣編寫的代碼不可移植,不能跨平台,比如windows的多線程程式拿到linux下是跑不起來的,相反也一樣,很不友善,然而C++11語言本身支援多線程,和平台無關,下面就來簡單認識多線程

# include<iostream>
# include<thread>//C++11本身支援的多線程,需包含該頭檔案

using namespace std;

void thread1();//子線程1(可調用對象作為線程的入口)

int main()
{
	//一個.exe可執行程式即為一個程序,一個程序可以有一個或多個線程,其中有一個主線程
	//這裡C++編寫完成生成.exe可執行程式,main中的代碼執行的就是主線程要做的事,我們自己進行多線程程式設計
	//可以定義一個函數,在函數中編寫要做什麼事的代碼,多線程是并發執行的
	thread myThread(thread1);//線程一旦被建立就開始執行了,與main并發執行的,在遇到join之前,
	//可能一會執行main函數,一會執行thread1函數,是不确定的,每次運作結果都可能不一樣
	cout << "主線程開始!" << endl;
	cout << "我是main函數1" << endl;
	cout << "我是main函數2" << endl;
	cout << "我是main函數3" << endl;
	cout << "我是main函數4" << endl;
	if(myThread.joinable())//可以加入時才能加入主線程
		myThread.join();//join表示加入,彙合,即加入程序
	//設想一下,一個程序可以包含多個線程,但隻有一個主線程,主線
	//程執行完畢,程序也就結束了,是以傳統多線程程式設計中,是讓主線程等待其它線程執行完畢,然後主線程才能結束
	//不然,主線程都結束了,該程序也就結束了,但是那些未結束的其他線程,怎麼辦?比如其他線程也在往程序輸入
	//輸出,而程序都結束了,肯定會出錯,抛出異常,主線程都執行完畢了,而子線程和主線程都屬于程序,程序都結束了
	//但是子線程還在執行,比如子線程要通路程序中的資源(螢幕,檔案等),這樣的程式肯定是不合格的!
	//join簡單來說,就是告訴主線程,我們是相關的,有關聯的,屬于同一程序的,你,必須等我執行完畢,才能繼續執行
	//因為屬于同一程序
	//一旦join,那麼main函數一旦執行完join,就會等待thread1執行完畢再繼續執行
	

	//
	cout << "我是main函數5" << endl;
	cout << "我是main函數6" << endl;
	cout << "我是main函數7" << endl;
	cout << "我是main函數8" << endl;
	cout << "主線程結束!" << endl;


	return 0;
}

void thread1()
{
	cout << "thread 1 begin!" << endl;
	cout << "I‘m thread 1 01" << endl;
	cout << "I‘m thread 1 02" << endl;
	cout << "I‘m thread 1 03" << endl;
	cout << "I‘m thread 1 04" << endl;
	cout << "I‘m thread 1 05" << endl;
	cout << "I‘m thread 1 06" << endl;
	cout << "thread 1 end!" << endl;
}
           
C++11多線程注意事項以及detach中的坑

結果很容易看到,執行順序是不确定的,因為主線程main和子線程thread1并發執行,就像兩條平行線往下一起執行,系統一會排程main線程,一會排程thread線程,但是,一旦調用join,主線程就會等待子線程運作結束,然後主線程在運作,直到結束,傳統程式設計就是如此,試想如果子線程通路主線程中的資源,如果主線程結束了,其中資源釋放,但是子線程還在運作,要通路資源,這樣的話肯定會出問題的,現在介紹detach,即分離的意思,一旦detach,就講子線程交給系統托管,與程序,主線程無關了,這種情況下,就很可能主線程結束,子線程還在運作,是以detach就引發出問題,程式設計的難度也加大了

C++11多線程注意事項以及detach中的坑

試着不調用join和deteach

C++11多線程注意事項以及detach中的坑

系統抛出異常,很簡單,一個.exe可執行程式就是一個程序,一個程序包含多個線程,其中有一個主線程,主線程結束了,程序也就結束了,但是子線程沒有detach,和主線程還是有關聯的,而主線程結束了,子線程還未結束,顯然是不允許的,抛出異常

接下來主要講detach

由于函數參數涉及到值傳遞和引用傳遞,是以問題就來了,看下面代碼

# include<iostream>
# include<thread>

using namespace std;

class A
{
public:
	//類型轉換構造函數
	A(int i):x(i)
	{
		cout <<"***********構造函數***********" <<"this:"<<this<<" "<< endl; 
	}
	A(const A&a)
	{
		cout << "*********拷貝構造函數*********" << "this:" << this << " " << endl;
	}
	~A() 
	{ 
		cout << "**********析構函數**********" << "this:" << this << " " << endl; 
	}
private:
	int x;
};

void fun(const int &i,const A &aa)//雖然這裡參數是A的引用,但是不起作用,運作時還是會
{
	cout << "i="<<i << endl;
	//cout << "p=" << p << endl;
	cout << "thread begin" << endl;
	cout << "thread01" << endl;
	cout << "thread02" << endl;
	cout << "thread03" << endl;
	cout << "thread04" << endl;
	cout << "thread end" << endl;
}
int main()
{
	int a = 4;
	int &i = a;
	A aObj(9);
	//thread t(fun, i,b);//隐式轉換,将int隐式轉換為A,什麼時候轉換呢?執行A的構造函數才轉換,先是傳入參數到
	//thread的構造函數(這時主線程main和子線程fun就開始并發執行了),然後再是将後面的參數傳到fun,這是fun的第二個參數是A類型的,而b是int的,這時會發生隐式類型轉換
	//而到了這個時候,由于detach,很可能主線程已經執行完畢了,子線程還沒構造完,是以fun裡a引用b,b在主線程結束後釋放了,沒有了,是以肯定會有問題
	thread t(fun, i, aObj);//但是如果A(b)這樣構造一個臨時對象,在執行thread的構造函數時就已經構造一個臨時變量
	//來接收b,就算detach後主線程結束,也不會有問題
	t.join();

	
	return 0;
}
           

将類作為一個參數傳遞給thread類的構造函數,可以看到傳遞時參數的複制情況,隻要在拷貝構造函數中輸出相應資訊即可,先看看結果

C++11多線程注意事項以及detach中的坑

倆個構造函數說明構造了兩個A的對象,一個是main函數裡的aobj對象這個很好了解,那麼另一個是哪裡的呢?子線程入口fun裡面參數是引用啊,按理來說不會産生臨時變量拷貝A啊,到底第二個A對象是怎麼來的呢,慢慢調試看

C++11多線程注意事項以及detach中的坑
C++11多線程注意事項以及detach中的坑
C++11多線程注意事項以及detach中的坑

接下來執行到此處

C++11多線程注意事項以及detach中的坑

果然,繼續調試,就轉到拷貝構造函數來了,其實fun函數裡傳遞引用,引用的是thread類構造時産生的臨時變量,并不是aObj,是以主線程結束的話,子線程就算沒結束也沒關系

再把join改為detach,看看主線程結束子線程沒結束是什麼結果

C++11多線程注意事項以及detach中的坑

嗯,看起來沒問題,把mian中代碼改一下,在t的第3個參數傳入一個整數,由于A定義了隻接受一個參數的構造函數,也就是類型轉換構造函數,

看看會輸出什麼

C++11多線程注意事項以及detach中的坑

W ! H ! A ! T !!!

為什麼呢?仔細思考,之前傳入aObj,在執行thread對象t的構造函數時,我們就拷貝aObj的臨時對象,注意注意,是在構造thread對象的時候就拷貝,這時thread t(fun,i,aObj)語句還沒執行完,是以會列印出很多資訊,但是此時換成了int型變量b,構造thread對象時不會将b轉換成A對象,而是在構造子線程,将參數i,b傳遞給fun時,才會對b進行轉換,而由于主線程中代碼較少,很快就結束了,甚至子線程還未構造main線程就結束了,是以才會有上面這種情況,

C++11多線程注意事項以及detach中的坑

現在應該清楚了,看教學視訊看到有的說是這裡子線程還未構造,主線程就結束了,那麼子線程在背景構造,但是b是main裡的資源,main線程結束了,是以b被釋放了,然後子線程再去通路b,是以容易出問題了,,我覺得這種說法不對,準确來說應該是構造thread時,參數會被複制到線程堆棧中供線程執行時存取,通路的是臨時變量,而不是b,是以不會出問題,不存在通路已經釋放的資源這個說法