多线程的注意事项
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