C++11多线程 多线程传参详解

x33g5p2x  于2022-03-06 转载在 其他  
字(5.7k)|赞(0)|评价(0)|浏览(566)

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

相关文章