多线程学习笔记2

6 篇文章 0 订阅

多线程的注意事项

1 多线程的基本介绍

class TA
{
public:
	TA(int &i) :m_i(i){
	cout << "ta()构造函数被执行" << endl;
	}
	TA(const TA &m_ta) :m_i(m_ta.m_i)
	{
		cout << "ta()拷贝构造函数被执行 " << endl;
	}
	void operator()()//可带参数也可不带参数,重载(),变为可调用对象
	{
		cout << "线程开始执行" << endl;
		cout << "m_i的值为:" << m_i << endl;//
    	cout << "m_i1的值为:" << m_i << endl;
		cout << "m_i2的值为:" << m_i << endl;
		cout << "线程结束执行" << endl;
	}
	int &m_i;//引用构造需要列表初始化
};

1 线程运行的开始和结束
程序运行起来,生成一个进程,进程所属的主线程开始自动执行
主线程从main函数开始执行,当我们创建自己的线程是的时候
需要创建一个函数
某个进程是否执行完毕,标志是主线程是否执行完毕
此时,一般情况下,如果其他子线程还未执行完毕,主线程执行完毕,操作系统会接管这些子线程
所以,一般情况下,我们希望保持主线程一致在执行

2 关于join和detach,joinable

// //thread:标准库的类,用于创建线程
// //join:加入 汇合 ,阻塞主线程,主线程等待子线程执行完毕,子线程执行完毕之后,才会继续执行主线程
// //一个书写良好的程序:应该是主线程等待子线程执行完毕之后,主线程最后收尾执行,正常退出

// //detach:分离,主线程不再与子线程汇合,不再等待子线程
// //detach后,子线程和主线程失去关联,驻留在后台,由C++运行时库接管
// //为什么引入detach,我们创建了很多子线程,主线程逐个等待子线程结束,这种编程方法不太好,所以引入了detach
// //但是一般情况下,再工程中不建议使用detach
// //一旦detach,与这个主线程关联的thread对象就会失去雨主线程的关联,此时,子线程会驻留在后台运行,驻留在后台的线程称之为守护线程
// //detach和join只能使用一种,一旦detach或者join不可以使用其他方式

//joinable()判断是否可以成功使用join()或者detach()
// //如果返回true,证明可以调用join()或者detach()
// //如果返回false,证明调用过join()或者detach(),join()和detach()都不能再调用了

3 创建线程的方法总结

1 函数

void myprint()
{
	cout << "线程开始执行" << endl;

	cout << "线程结束执行" << endl;
	cout << "线程开始执行1" << endl;

	cout << "线程结束执行1" << endl;
	cout << "线程开始执行2" << endl;

	cout << "线程结束执行2" << endl;
	cout << "线程开始执行3" << endl;

	cout << "线程结束执行3" << endl;
}
thread mytobj(myprint);// 创建了线程mytobj,myprint是可调用对象,是线程执行的入口,

函数可以直接作为线程的调用方式,函数也可以带参数,关于参数的注意事项在下一节会讲

2 类对象:用类对象,重载()符,表示为类对象变为可调用函数

class TA
{
public:
	TA(int &i) :m_i(i){
		cout << "ta()构造函数被执行" << endl;
	}
	TA(const TA &m_ta) :m_i(m_ta.m_i)
	{
		cout << "ta()拷贝构造函数被执行 " << endl;
	}
	void operator()()//不能带参数,重载(),变为可调用对象
	{
		cout << "线程开始执行" << endl;
		cout << "m_i的值为:" << m_i << endl;//
		cout << "m_i1的值为:" << m_i << endl;
		cout << "m_i2的值为:" << m_i << endl;
		cout << "线程结束执行" << endl;
	}
	int &m_i;//引用构造需要列表初始化
};

TA ta(i);
thread myobj2(ta);//类对象作为可调用对象
myobj2.detach();

当类作为可调用对象的时候,要非常注意参数的传递,尤其是在使用detach的时候。

3 lambda

	auto mylamthread = []
	{
		cout << "线程结束执行2" << endl;
		//...

		cout << "线程开始执行3" << endl;
	};
	thread myobj4(mylamthread);
	myobj4.join();

4 detach的注意事项

> 1 传递临时对象要避免的陷阱

1.1 要避免的陷阱1:指针和引用都尽可能避免使用:
指针在传递的时候地址相同,假如使用detach分离子线程,假如主线程先结束,会导致子线程传递已被操作系统回收的临时变量,导致崩溃或者出错

1.2 要避免的陷阱2:避免隐式转换

	int i = 12;
	int &m = i;
	char buff[] = "this is a test!";
	//buff什么时候转换为string的 detach的时候,假如buff回收了,再转为string,会导致程序变为危险状态
	//thread myobj(myprint,i,buff);
	//显示转换string会优先执行,不会导致程序出现崩溃bug,隐式转换会有bug
	//可以看A int对象的结论。如果使用int这类简单型的参数,直接使用值传递,不要引用
	//如果传递类对象,避免隐式类型转换,直接显示转换。
	//使用detach需要小心谨慎处理
	//建议使用join,不要使用detach,避免出现内存回收的问题
    thread myobj1(myprint,i,string(buff));//工程上假如这样的话调用的buff会是正常的

2 函数对象作为参数传递

//3.1 为了数据的安全考虑,传递类对象的时候,都是拷贝一份新的类对象
//因此,子线程的数据不会影响主线程的数据
//3.2 ref函数,会根据引用传递,变为真引用
//myPrint(const A& pmybuf)中引用不能去掉,如果去掉会多创建一个对象
//const也不能去掉,去掉会出错
//即使是传递的const引用,但在子线程中还是会调用拷贝构造函数构造一个新的对象,
//所以在子线程中修改m_i的值不会影响到主线程
//如果希望子线程中修改m_i的值影响到主线程,可以用thread myThread(myPrint, std::ref(myObj));
//这样const就是真的引用了,myPrint定义中的const就可以去掉了,类A定义中的mutable也可以去掉了

函数对象作为参数传递的时候,要使用显示转换,并且形参要用引用const A&a 作为传参。不使用引用的时候,主线程会调用一次拷贝构造函数,子线程也会调用一次拷贝构造函数

函数对象的成员变量改变不会影响外部的变量改变,因为thread是限拷贝一份,如果希望可以直接引用的话,需要用ref函数

thread myjob(myprint,std::ref(x));

3 智能指针作为参数传递:
只能用join,假如是detach的话,在主线程new结束后,堆回收,会导致子线程bug

i

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值