C++底層接口Thread類詳細使用方法

頭文件<thread>

創建thread對象

thread有三個構造函數,分別是默認構造函數,複製構造函數,和帶參數的構造函數.

在這裏插入圖片描述

創建和使用一個thread實例類似如下:

void print()
{
	cout << "新線程啓動" << endl;
	cout << "新線程結束" << endl;
}
int main()
{
	thread t(print);
	t.join();
	cout << "主線程結束\n";
	cout << endl;
}

在這裏插入圖片描述

其中第一個參數是可調用對象(函數、成員函數、函數對象、lambda)

當創建出一個thread對象時,C++就自動將它儘可能啓動於一個新線程,如果沒有成功,則拋出異常std::system_error.

成員函數join()的使用方法

join中文直譯意思是加入、結合、連接.

顧名思義,使用這個成員函數,子線程會和主線程匯合,也就是說主線程會等待子線程結束,在子線程結束之前,主線程不會結束.

使用Join,我們可以確保子線程能夠完全的運行,如果主線程走的快,主線程就會等待子線程的完畢,最後主線程和子線程都能運行完畢.

成員函數detach()的使用方法

detach中文直譯意思是分離、分開.

顧名思義,我們各走各的,你走你的,我走我的。也就是說主線程不會等待子線程,即使主線程結束的時候子線程還沒有結束。

一但使用detach ,與主線程相關的thread對象就會失去與主線程的關聯,此時這個子線程就會駐留在後臺運行,相當於被C++運行時庫接管,當這個子線程執行完畢後,由運行時庫負責清理該線程相關的資源.

因此使用detach會帶來很多的問題,比如:

void print()
{
	cout << "子線程1" << endl;
	cout << "子線程2" << endl;
	cout << "子線程3" << endl;
	cout << "子線程4" << endl;
	cout << "子線程5" << endl;
	cout << "子線程6" << endl;
	cout << "子線程7" << endl;
	cout << "子線程8" << endl;
	cout << "子線程9" << endl;
}
int main()
{

	thread t(print);
	t.detach();
	cout << "主線程1\n";
	cout << "主線程2\n";
	cout << "主線程3\n";
	cout << "主線程4\n";
	cout << "主線程5\n";
	cout << "主線程6\n";
	cout << endl;
}

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

可以看到每次運行都會有不同的結果


傳遞臨時對象作爲線程參數帶來的問題

當可調用對象使用引用時,還會帶來其他的結果:

class A
{
public:
	int &num;
public:
	A(int &i) :num(i) {}
	void operator()()
	{
		cout << num << endl;
		cout << num << endl;
		cout << num << endl;
	}
};

int main()
{
	int n = 1;
	A a(n);
	thread t(a);
	t.detach();
	cout << "hello world" << endl;
	return 0;
}

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

可以看到,當主線程結束的時候,此時引用的n已經被消耗了,此時訪問引用就會帶來無法預料的結果.

還有個疑問:當主線程結束時,上面的a對象不是已經被銷燬了嗎?爲什麼還能調用?

實際上,子線程調用的對象實際上是複製到子線程中的,當主線程結束以後原來的對象確實已經被銷燬了,但是複製的對象沒有消耗,還是能使用的。

我們可以使用一個例子來論證一下:

class A
{
public:
	int &num;
public:
	A(int &i) :num(i) { cout << "帶參構造函數調用" << endl; }
	A(const A& other) :num(other.num) { cout << "複製構造函數調用" << endl; }
	~A()
	{
		cout << "析構函數調用" << endl;
	}
	void operator()()
	{
		cout << num << endl;
		cout << num << endl;
		cout << num << endl;
	}
};

int main()
{
	int n = 1;
	A a(n);
	thread t(a);
	t.join();
	cout << "hello world" << endl;
	return 0;
}

在這裏插入圖片描述

當調用參數時,會出現你想象不到的結果:

void print(const int & i, char * p)
{
	cout << i << endl;

	cout << p << endl;
	return;
}
int main()
{

	int n = 1;
	int& m = n;
	char c[] = "test!";
	thread t(print, m, c);
	t.detach();
	cout << "hello world" << endl;
	return 0;
}

得到的地址如下:

&n	0x00affa2c {1}
&m	0x00affa2c {1}
&i	0x00f0f444 {1}	
c	0x00f3f978  "test!"
p	0x00f3f978 "test!"	



可以知道i並不是n的引用,實際上是值傳遞,即使主線程detach了子線程,那麼子線程中用i值仍然是安全的。

但是p和c卻是相同的地址,當主線程完成後 c的內存就被釋放,這個時候訪問p就會有很大的危險

如果換成string呢會怎麼樣?

void print(const int & i, const string & p)
{
	cout << i << endl;

	cout << p.c_str() << endl;
	return;
}

int main()
{
	int n = 1;
	int& m = n;
	char c[] = "test!";
	thread t(print, m, c);
	t.detach();
	cout << "hello world" << endl;
	return 0;
}
c	0x0056fd20 "test!"	char[6]
&p	0x0079f7b0 "test!"	const std::basic_string<char,std::char_traits<char>,std::allocator<char> > *

可以看到用string得到的結果是不一樣的,看上去string沒有引用主線程的c,好像安全了。問題是真的安全了?

有一個問題很有可能被忽略了,c是什麼時候轉換成string p的呢?很有可能c被回收瞭然後還沒有進行轉換

解決辦法:使用臨時對象,避免隱式轉換:

thread t(print, m, string(c));

驗證如下:

class A
{
public:
	int num;
public:
	A(int i) :num(i) { cout << "帶參構造函數調用" << endl; }
	A(const A& other) :num(other.num) { cout << "複製構造函數調用" << endl; }
	~A()
	{
		cout << "析構函數調用" << endl;
	}
	void operator()()
	{
		cout << num << endl;
		cout << num << endl;
		cout << num << endl;
	}
};
void print(const int & i, const A & p)
{
	cout << i << endl;
	cout << p.num << endl;
	return;
}
int main()
{
	int n = 1;
	int& m = n;
	char c[] = "test!";
	thread t(print, m, A(10));
	t.detach();
	cout << "hello world" << endl;
	return 0;
}

在這裏插入圖片描述

總結:

1.如果傳遞int這種簡單類型,建議都是按值傳遞,不要用引用.
2.如果傳遞類對象,避免隱式類型轉換,在創建線程這一行就構建出臨時對象,然後參數使用引用,不然會在多構造出一個對象。
3.避免麻煩,儘量別使用detach

線程ID

線程id是一個數字,每一個線程都有自己的id.

使用std::this_thread::get_id()來獲取,

class A
{
private:
	int m;
public:
	A(int a) :m(a) { cout << "帶參數構造函數執行,線程ID是: " << this_thread::get_id() << endl; }
	A(const A& other) :m(other.m) {
		cout << "複製構造函數執行,線程ID是: " << this_thread::get_id() << endl;
	}
	A() {cout << "默認構造函數執行,線程ID是: " << this_thread::get_id() << endl; }
	~A() { cout << "析構函數執行,線程ID是: " << this_thread::get_id() << endl; }

};
void print(const A & a)
{
	cout << "子線程print的參數地址是:" << &a << ",子線程id是: " << this_thread::get_id() << endl;
}
int main()
{
	cout << "主線程id是: " << this_thread::get_id() << endl;
	int n = 1;
	thread m(print, n);
	m.join();
}

在這裏插入圖片描述

可以看到A對象是在子線程中構造的,如果使用detach,就會出現主線程結束而子線程還沒構造對象的情況,此時調用n就會出現不可預料的結果

##如果換成臨時對象呢?

	thread m(print, A(n));

在這裏插入圖片描述

可以看到主線程進行了帶參構造和複製構造,就不會有上面的問題

std::ref函數

class A
{
public:
	mutable int m;
public:
	A(int a) :m(a) { cout << "帶參數構造函數執行,線程ID是: " << this_thread::get_id() << endl; }
	A(const A& other) :m(other.m) {
		cout << "複製構造函數執行,線程ID是: " << this_thread::get_id() << endl;
	}
	A() {cout << "默認構造函數執行,線程ID是: " << this_thread::get_id() << endl; }
	~A() { cout << "析構函數執行,線程ID是: " << this_thread::get_id() << endl; }

};
void print(const A & a)
{
	a.m = 100;
	cout << "子線程print的參數地址是:" << &a << ",子線程id是: " << this_thread::get_id() << endl;
}
int main()
{
	cout << "主線程id是: " << this_thread::get_id() << endl;
	int n = 1;
	A a(n);
	thread m(print, a);
	m.join();
}
//		&a	0x004ffe24 {m=1 }	A *
//		&a	0x0081fc50 {m=100 }	const A *


可以看到即使傳遞是引用,子線程的修改不會對主線程造成任何影響。如果真的想修改需要使用std::ref函數

void print(const A & temp)
{
	temp.m = 100;
	cout << "子線程print的參數地址是:" << &temp << ",子線程id是: " << this_thread::get_id() << endl;
}
int main()
{
	cout << "主線程id是: " << this_thread::get_id() << endl;
	int n = 1;
	A a(n);
	thread m(print, ref(a));
	m.join();
}
//		&temp	0x004ff9c0 {m=100 }	const A *
//		&a	    0x004ff9c0 {m=100 }	A *

智能指針做參數

在這裏插入圖片描述

可以看到無法編譯通過,解決辦法:使用std::move

注意:這種情況不要使用detach了,會有危險。


成員函數做線程參數

class A
{
public:
	mutable int m;
public:
	A(int a) :m(a) { cout << "帶參數構造函數執行,線程ID是: " << this_thread::get_id() << endl; }
	A(const A& other) :m(other.m) {
		cout << "複製構造函數執行,線程ID是: " << this_thread::get_id() << endl;
	}
	A() {cout << "默認構造函數執行,線程ID是: " << this_thread::get_id() << endl; }
	~A() { cout << "析構函數執行,線程ID是: " << this_thread::get_id() << endl; }
	void print(int n)
	{
		cout << "子線程print執行 " << this << " 線程id = " << this_thread::get_id() << endl;
	}
};
void print(unique_ptr<int>p)
{
}
int main()
{
	cout << "主線程id是: " << this_thread::get_id() << endl;
	A myobj(10);
	thread t(&A::print, myobj, 15);

	t.join();
}

在這裏插入圖片描述

總結:不要在detach的時候使用 ref.

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