先分析参数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时,最好不使用引用和指针。
当我们将子线程执行函数中的第二个参数,即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了,主线程结束了,子线程不会使用无效参数
**每个线程实际上都对应一个数字,每个线程对应的数字都不同。
线程id可以使用标准函数获取 std::this_thread::get_id()
为了验证隐式类型转换是在子线程中进行的,我们可以使用获取线程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,也可以安全使用
#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; }