线程传参详解,detach()大坑,成员函数做线程函数
1.传递临时对象作为线程参数
1.1 要避免的陷阱(解释1)
先分析参数i,分析可知,对于i来说 线程中的函数的本来是要使用引用传递,但是实际上使用的是值传递,因此即便主线程中使用了detach使主线程先结束,子线程依然是安全的。
#include "pch.h"
#include <iostream>
#include <thread>
#include <string>
#include <vector>
#include <map>
using namespace std;
void myprint(const int &i, char *pmybuf)
{
//分析认为 i并不是mavr的引用,实际是值传递
//即便主线程中detach,那么子线程依然是安全的
cout << i << endl;
cout << pmybuf << endl;
}
int main()
{
//一:传递临时对象作为线程参数
//1.1要避免的陷阱(解释1)
int mvar = 1;
int& mvary = mvar;
char mybuf[] = "this is a test!";
thread mytobj(myprint, mvar, mybuf);
//mytobj.join();
mytobj.detach(); //子线程和主线程分别执行
cout << "I LOVE CHINA" << endl;
return 0;
}
使用shift+F9 参看相应参数
再来看参数pmybuf,对于原字符数组 ,地址为affc38
(设置两个断点,F5快速到下一个断点)
而传入函数中数组的地址为:
两者地址相同,这里使用了引用传递,这导致子线程是不安全的。所以在使用detach时,最好不使用引用和指针。
1.2 要避免的陷阱2
当我们将子线程执行函数中的第二个参数,即char * pmybuf修改为const string &pmybuf时,我们再查看进入线程前后的地址。进入子线程前的地址和进入子线程后的地址不相同,说明是值传递,但是这样的子线程真的安全吗?
事实上存在mybuf都被回收了(main函数执行完了),系统才用mybuf去转string的情况
#include "pch.h"
#include <iostream>
#include <thread>
#include <string>
#include <vector>
#include <map>
using namespace std;
void myprint(const int &i, const string &pmybuf)
{
//分析认为 i并不是mavr的引用,实际是值传递
//即便主线程中detach,那么子线程依然是安全的
cout << i << endl;
cout << pmybuf << endl; //指针在detach指针时,绝对有问题
}
int main()
{
//一:传递临时对象作为线程参数
//1.1要避免的陷阱(解释1)
//1.2 要避免的陷阱(解释2)
int mvar = 1;
int& mvary = mvar;
char mybuf[] = "this is a test!";
thread mytobj(myprint, mvar, mybuf); //mybuf具体在什么时候转换成了string
//事实上存在,mybuf都被回收了(main函数执行完了),系统才用mybuf去转string
//mytobj.join();
mytobj.detach(); //子线程和主线程分别执行
cout << "I LOVE CHINA" << endl;
return 0;
}
那应该怎么才是稳定的呢?
首先我们将代码做一下修改:
创建A类 分别写出有参构造 拷贝构造和析构函数,然后在线程函数中传入需要的参数,使用join()函数,结果如图:
#include "pch.h"
#include <iostream>
#include <thread>
#include <string>
#include <vector>
#include <map>
using namespace std;
class A
{
public:
int m_i;
//类型转换构造函数 将int整形转换成类对象
A(int a) :m_i(a) //初始化变量m_i 相当于在函数内实现了m_i = a
{
cout << "[A::A(int a)构造函数执行]" << endl;
}
A(const A &a) :m_i(a.m_i) { cout << "[A::A(const A)拷贝构造函数执行]" << endl; }
~A()
{
cout << "[A::~A(析构函数执行]" << endl;
}
};
//void myprint(const int &i, const string &pmybuf)
//{
// //分析认为 i并不是mavr的引用,实际是值传递
// //即便主线程中detach,那么子线程依然是安全的
// cout << i << endl;
// cout << pmybuf << endl; //指针在detach指针时,绝对有问题
//}
void myprint(const int i, const A &pmybuf)
{
cout << &pmybuf << endl; //打印pmybuf对象的地址
return;
}
int main()
{
//一:传递临时对象作为线程参数
//1.1要避免的陷阱(解释1)
//1.2 要避免的陷阱(解释2)
//int mvar = 1;
//int& mvary = mvar;
//char mybuf[] = "this is a test!";
//thread mytobj(myprint, mvar, mybuf); //mybuf具体在什么时候转换成了string
//事实上存在,mybuf都被回收了(main函数执行完了),系统才用mybuf去转string
//我们这里直接将mybuf转换成string对象,这是一个可以保证在线程中用肯定有效的对象
//用自定义的类class A来求证
//thread mytobj(myprint, mvar, string(mybuf));
int mvar = 1;
int mysecondpar = 12;
thread mytobj(myprint, mvar, mysecondpar); //这是希望将整形转换成A类对象传递给myprint函数的第二个参数
mytobj.join();
cout << "I LOVE CHINA" << endl;
return 0;
}
先执行构造函数,再执行析构函数,通过有参构造函数成功的将int类型的mysecondpar转换成A类对象。
将join函数改为detach函数后,将主函数里的cout注释掉,使主函数快速结束,能观察到如下结果:
没有打印任何东西,说明线程执行函数内还没来得及通过有参构造来创建类对象,主线程就结束了,这里子线程就会出现问题。
解决这个问题的办法是创建临时对象
#include "pch.h"
#include <iostream>
#include <thread>
#include <string>
#include <vector>
#include <map>
using namespace std;
class A
{
public:
int m_i;
//类型转换构造函数 将int整形转换成类对象
A(int a) :m_i(a) //初始化变量m_i 相当于在函数内实现了m_i = a
{
cout << "[A::A(int a)构造函数执行]" << endl;
}
A(const A &a) :m_i(a.m_i) { cout << "[A::A(const A)拷贝构造函数执行]" << endl; }
~A()
{
cout << "[A::~A(析构函数执行]" << endl;
}
};
//void myprint(const int &i, const string &pmybuf)
//{
// //分析认为 i并不是mavr的引用,实际是值传递
// //即便主线程中detach,那么子线程依然是安全的
// cout << i << endl;
// cout << pmybuf << endl; //指针在detach指针时,绝对有问题
//}
void myprint(const int i, const A &pmybuf)
{
cout << &pmybuf << endl; //打印pmybuf对象的地址
return;
}
int main()
{
//一:传递临时对象作为线程参数
//1.1要避免的陷阱(解释1)
//1.2 要避免的陷阱(解释2)
//int mvar = 1;
//int& mvary = mvar;
//char mybuf[] = "this is a test!";
//thread mytobj(myprint, mvar, mybuf); //mybuf具体在什么时候转换成了string
//事实上存在,mybuf都被回收了(main函数执行完了),系统才用mybuf去转string
//我们这里直接将mybuf转换成string对象,这是一个可以保证在线程中用肯定有效的对象
//用自定义的类class A来求证
//thread mytobj(myprint, mvar, string(mybuf));
int mvar = 1;
int mysecondpar = 12;
//thread mytobj(myprint, mvar, mysecondpar); //这是希望将整形转换成A类对象传递给myprint函数的第二个参数
thread mytobj(myprint, mvar, A(mysecondpar)); //通过创建临时对象来解决上面这个问题
//mytobj.join();
mytobj.detach();
//cout << "I LOVE CHINA" << endl;
return 0;
}
运行结果
这里说明了临时对象是在主线程中被创建的,即创建线程的同时,通过构造临时对象来传递参数的方法时可行。
事实1:只要用临时构造的A类对象作为参数传递给子线程,那么这一定能够在主线程结束前把子线程的第二个参数构件出来,从而确保即便是detach了,主线程结束了,子线程不会使用无效参数
1.3 总结
1.若传递int这种简单类型参数,建议直接用值传递,不要用引用。
2.如果传递类对象,避免隐式类型转换。要在创建线程这一行构造出临时对象来,然后在函数参数里用引用来接,否则系统还会构造一次。
3. 建议不使用detach,只使用join,这样就不存在局部变量失效导致线程对内存的非法引用。
2.临时对象作为线程参数继续讲
2.1线程id的概念
**每个线程实际上都对应一个数字,每个线程对应的数字都不同。
线程id可以使用标准函数获取 std::this_thread::get_id()
2.2临时对象构造时机抓捕
为了验证隐式类型转换是在子线程中进行的,我们可以使用获取线程id的方法,代码如下:**
#include "pch.h"
#include <iostream>
#include <thread>
#include <string>
#include <vector>
#include <map>
using namespace std;
class A
{
public:
int m_i;
//类型转换构造函数 将int整形转换成类对象
A(int a) :m_i(a) //初始化变量m_i 相当于在函数内实现了m_i = a
{
cout << "[A::A(int a)构造函数执行]" <<this<<"this_threadid"<<this_thread::get_id()<< endl;
}
A(const A &a) :m_i(a.m_i) { cout << "[A::A(const A)拷贝构造函数执行]" <<this<< "this_threadid" << this_thread::get_id() << endl; }
~A()
{
cout << "[A::~A(析构函数执行]" <<this<< "this_threadid" << this_thread::get_id() << endl;
}
};
//void myprint(const int &i, const string &pmybuf)
//{
// //分析认为 i并不是mavr的引用,实际是值传递
// //即便主线程中detach,那么子线程依然是安全的
// cout << i << endl;
// cout << pmybuf << endl; //指针在detach指针时,绝对有问题
//}
//void myprint(const int i, const A &pmybuf)
//{
//
// cout << &pmybuf << endl; //打印pmybuf对象的地址
// return;
//}
void myprint2(const A &pmybuf)
{
cout <<"子线程参数地址为:"<< &pmybuf << endl; //打印pmybuf对象的地址
cout << "threadid = " << this_thread::get_id() << endl;
return;
}
int main()
{
//一:传递临时对象作为线程参数
//1.1要避免的陷阱(解释1)
//1.2 要避免的陷阱(解释2)
//int mvar = 1;
//int& mvary = mvar;
//char mybuf[] = "this is a test!";
//thread mytobj(myprint, mvar, mybuf); //mybuf具体在什么时候转换成了string
//事实上存在,mybuf都被回收了(main函数执行完了),系统才用mybuf去转string
//我们这里直接将mybuf转换成string对象,这是一个可以保证在线程中用肯定有效的对象
//用自定义的类class A来求证
//thread mytobj(myprint, mvar, string(mybuf));
//int mvar = 1;
//int mysecondpar = 12;
thread mytobj(myprint, mvar, mysecondpar); //这是希望将整形转换成A类对象传递给myprint函数的第二个参数
//thread mytobj(myprint, mvar, A(mysecondpar)); //通过创建临时对象来解决上面这个问题
//mytobj.join();
cout << "主线程id为:" << this_thread::get_id() << endl;
int mvar = 1;
std::thread mytobj(myprint2, mvar);
mytobj.join();
//cout << "I LOVE CHINA" << endl;
return 0;
}
结果为下图,可以看出将int转换为A类的过程是在子线程中进行的。
如果改为std::thread mytobj(myprint2, A(mvar)); 观察结果
用了临时对象后,可以安全使用。
即便使用detach,也可以安全使用
3.传递类对象、智能指针作为线程参数
#include "pch.h"
#include <iostream>
#include <thread>
#include <string>
#include <vector>
#include <map>
using namespace std;
class A
{
public:
mutable int m_i; //mutable关键字可以修改const常量
//类型转换构造函数 将int整形转换成类对象
A(int a) :m_i(a) //初始化变量m_i 相当于在函数内实现了m_i = a
{
cout << "[A::A(int a)构造函数执行]" <<this<<"this_threadid"<<this_thread::get_id()<< endl;
}
A(const A &a) :m_i(a.m_i) { cout << "[A::A(const A)拷贝构造函数执行]" <<this<< "this_threadid" << this_thread::get_id() << endl; }
~A()
{
cout << "[A::~A(析构函数执行]" <<this<< "this_threadid" << this_thread::get_id() << endl;
}
};
//void myprint(const int &i, const string &pmybuf)
//{
// //分析认为 i并不是mavr的引用,实际是值传递
// //即便主线程中detach,那么子线程依然是安全的
// cout << i << endl;
// cout << pmybuf << endl; //指针在detach指针时,绝对有问题
//}
//void myprint(const int i, const A &pmybuf)
//{
//
// cout << &pmybuf << endl; //打印pmybuf对象的地址
// return;
//}
void myprint2(const A &pmybuf)
{
pmybuf.m_i = 199;
cout <<"子线程参数地址为:"<< &pmybuf << endl; //打印pmybuf对象的地址
cout << "threadid = " << this_thread::get_id() << endl;
return;
}
int main()
{
//一:传递临时对象作为线程参数
//1.1要避免的陷阱(解释1)
//1.2 要避免的陷阱(解释2)
//int mvar = 1;
//int& mvary = mvar;
//char mybuf[] = "this is a test!";
//thread mytobj(myprint, mvar, mybuf); //mybuf具体在什么时候转换成了string
//事实上存在,mybuf都被回收了(main函数执行完了),系统才用mybuf去转string
//我们这里直接将mybuf转换成string对象,这是一个可以保证在线程中用肯定有效的对象
//用自定义的类class A来求证
//thread mytobj(myprint, mvar, string(mybuf));
//int mvar = 1;
//int mysecondpar = 12;
thread mytobj(myprint, mvar, mysecondpar); //这是希望将整形转换成A类对象传递给myprint函数的第二个参数
//thread mytobj(myprint, mvar, A(mysecondpar)); //通过创建临时对象来解决上面这个问题
//mytobj.join();
/*cout << "主线程id为:" << this_thread::get_id() << endl;
int mvar = 1;
std::thread mytobj(myprint2, A(mvar));
mytobj.join();*/
A myobj(10); //生成一个类A对象
thread mytobj(myprint2, myobj); //myobj将类对象作为线程参数 调用了拷贝构造 主线程结束不影响子线程进行
mytobj.join();
//cout << "I LOVE CHINA" << endl;
return 0;
}