17.3 线程传参详解、detach坑与成员函数作为线程函数

一:传递临时对象作为线程参数

<1>要避免的陷阱(解释1)

void myprint(const int& i, char* pmybuf)
{
	cout << i << endl;
	cout << pmybuf << endl;
	return;
}

int main()
{
	int mvar = 1;
	int& mvary = mvar;
	char mybuf[] = "this is test!";
	thread mytobj(myprint, mvar, mybuf);
	//mytobj.join();
	mytobj.detach();
	cout << "main主函数执行结束" << endl;
	return 0;
}

这是有问题的。

void myprint(const int& i, const string& pmybuf)
{
	cout << i << endl;
	cout << pmybuf << endl;
	const char* ptmp = pmybuf.c_str();
	cout << pmybuf.c_str() << endl;
	return;
}

int main()
{
	int mvar = 1;
	int& mvary = mvar;
	char mybuf[] = "this is test!";
	thread mytobj(myprint, mvar, mybuf);
	//mytobj.join();
	mytobj.detach();
	cout << "main主函数执行结束" << endl;
	return 0;
}

这是没有问题的。

<2>要避免的陷阱(解释2)
事实1:只要用临时构造的类A对象作为参数传递给线程,那么就一定能够在主线程执行完毕前把线程的第二个参数构建出来,从而确保即便detach()子线程也安全运行。

class A
{
public:
	A(int a) :m_i(a)
	{
		cout << "A(int a) :m_i(a)构造函数执行" << this << endl;
	}
	A(const A& a)
	{
		cout << "A(const A& a)拷贝构造函数执行" << this << endl;
	}
	~A()
	{
		cout << "~A()析构函数执行" << this << endl;
	}

public:
	int m_i;
};

void myprint(const int& i, const A& pmybuf)
{
	cout << i << endl;
	cout << pmybuf.m_i << endl;
	cout << &pmybuf << endl;
	return;
}

int main()
{
	int mvar = 1;
	int mysecondpar = 12;
	thread mytobj(myprint, mvar, A(mysecondpar));
	//mytobj.join();
	mytobj.detach();
	cout << "main主函数执行结束" << endl;
	return 0;
}
A(int a) :m_i(a)构造函数执行00000047BC6FFB64
A(const A& a)拷贝构造函数执行000001ADA3B51F80
~A()析构函数执行00000047BC6FFB64
main主函数执行结束

ps:
发现void myprint(const int& i, const A& pmybuf),
A对象是否是引用,都会构建拷贝构造函数。书上说是由于thread引起的。暂时不解。
若A对象没有引用,则会调用两次拷贝构造函数,第二次调用感觉有问题。

<3>总结
1>如果传递int这种简单类型参数,建议都是值传递,不要用引用,防止节外生枝;
2>如果传递类对象,避免隐式类型转换。全部都在创建线程这一行就构建出临时对象来,然后在函数参数里,用引用来接,否则系统还会多构造一次对象,浪费;
终极结论:
3>建议不使用detach(),只使用join();这样就不存在局部变量失效导致线程对内存的非法引用问题。

二:临时对象作为线程参数继续讲

常用测试大法
<1>线程id概念:id是个数字,每个线程(不管是主线程还是子线程)实际上都对应着一个数字,而且每个线程对应的数字都不同;也就是说不同的线程,它的线程id(数字)必然是不同;
线程id可以用C++标准库里的函数来获取,std::this_thread::get_id()来获取;
<2>临时对象构造时机捕获

class A
{
public:
	A(int a) :m_i(a)
	{
		cout << "A(int a) :m_i(a)构造函数执行, this = " << this << ", threadId = " << this_thread::get_id() << endl;
	}
	A(const A& a)
	{
		cout << "A(const A& a)拷贝构造函数执行" << this << ", threadId = " << this_thread::get_id() << endl;
	}
	~A()
	{
		cout << "~A()析构函数执行" << this << ", threadId = " << this_thread::get_id() << endl;
	}

public:
	int m_i;
};

void myprint2(const A& pmybuf)
{
	cout << "子线程myprint2的参数pmybuf的地址是: " << &pmybuf << ", threadId = " << this_thread::get_id() << endl;
	return;
}

int main()
{
	cout << "主线程id = " << this_thread::get_id() << endl;
	int mvar = 1;
	//thread mytobj(myprint2, mvar);
	thread myobj2(myprint2, A(mvar));
	//mytobj2.join();  //用join方便观察
	myobj2.detach();
	Sleep(5000);
	cout << "main主函数执行结束!" << endl;
	return 0;
}
主线程id = 30476
A(int a) :m_i(a)构造函数执行, this = 000000DF09B8FC74, threadId = 30476
A(const A& a)拷贝构造函数执行000001E8C9E321B0, threadId = 30476
~A()析构函数执行000000DF09B8FC74, threadId = 30476
子线程myprint2的参数pmybuf的地址是: 000001E8C9E321B0, threadId = 14648
~A()析构函数执行000001E8C9E321B0, threadId = 14648
main主函数执行结束!

若将void myprint2(const A& pmybuf)更改为void myprint2(const A pmybuf);

主线程id = 18584
A(int a) :m_i(a)构造函数执行, this = 00000015E115FB14, threadId = 18584
A(const A& a)拷贝构造函数执行000001526AF927A0, threadId = 18584
~A()析构函数执行00000015E115FB14, threadId = 18584
A(const A& a)拷贝构造函数执行00000015E14FF584, threadId = 30096
子线程myprint2的参数pmybuf的地址是: 00000015E14FF584, threadId = 30096
~A()析构函数执行00000015E14FF584, threadId = 30096
~A()析构函数执行000001526AF927A0, threadId = 30096
main主函数执行结束!

三:传递类对象与智能指针作为线程参数
std::ref()函数

四:用成员函数指针做线程函数

int myvar = 1;
int& mvary = myvar;

char mybuf[] = “this is a test”;
//thread myobj(myprint, myvar, mybuf); // 但是mybuf到底是在什么时候转成string
// 事实上存在,mybuf都被回收了(main函数执行完了),系统才用mybuf去转string的可能性;
thread mytobj(myprint, myvar, string(mybuf)); // 我们这里直接将mybuf转换成string对象,这是一个可以保证在线程中肯定有效的对象。
mytobj.join();
mytobj.detach(); //子线程和主线程分别执行。

int var = 1;
int secondvar = 12;
thread mytobj(myprint, var, secondvar); // 我们希望secondvar转成A对象传递给myprint的第二个参数
thread mytobj(myprint, var, static_cast(secondvar));
在创建线程的同时构造临时对象的方法传递参数是可行的
mytobj.join();
mytobj.detach();

cout << "主线程id: " << this_thread::get_id() << endl;
int mvar = 1;
thread mytobj2(myprint2, A(mvar));
//mytobj2.join();
mytobj2.detach();

A pa(10); //生成一个类对象;
thread mytobj3(myprint2, std::ref(pa)); //pa将类对象作为线程参数
mytobj3.join();

unique_ptr p(new int(100));
thread mytobj4(myprint3, std::move§);
mytobj4.join();

A pa(10);
thread mytobj5(&A::thread_work, &pa, 15); //&pa == std::ref()
mytobj5.join();

A pa(10);
thread mytobj6(std::ref(pa), 5); // 不再调用拷贝构造函数了,那么后续如果调用detach就不安全了
mytobj6.join();

A pa(10);
thread mytobj7(pa, 15);
mytobj7.join();

cout << “I love China” << endl;
cout << "主线程ID为: " << this_thread::get_id() << endl;

你可能感兴趣的:(c++学习,c++)