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));

在这里插入图片描述

扫描二维码关注公众号,回复: 11339234 查看本文章

可以看到主线程进行了带参构造和复制构造,就不会有上面的问题

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.

猜你喜欢

转载自blog.csdn.net/qq_44800780/article/details/104736457