C++ 并发与多线程(二)
2021/5/3 14:25:32
本文主要是介绍C++ 并发与多线程(二),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
线程传参详解,detach()大坑,成员函数做线程函数
- 1.传递临时对象作为线程参数
- 1.1 要避免的陷阱(解释1)
- 1.2 要避免的陷阱2
- 1.3 总结
- 1.若传递int这种简单类型参数,建议直接用值传递,不要用引用。
- 2.如果传递类对象,避免隐式类型转换。要在创建线程这一行构造出临时对象来,然后在函数参数里用引用来接,否则系统还会构造一次。
- 3. 建议不使用detach,只使用join,这样就不存在局部变量失效导致线程对内存的非法引用。
- 2.临时对象作为线程参数继续讲
- 2.1线程id的概念
- 2.2临时对象构造时机抓捕
- 3.传递类对象、智能指针作为线程参数
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; }
这篇关于C++ 并发与多线程(二)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-06-23AI大模型企业应用实战(14)-langchain的Embedding
- 2024-06-23AI大模型企业应用实战(15)-langchain核心组件
- 2024-06-23AI大模型企业应用实战(16)-langchain核心组件
- 2024-06-23AI 大模型企业应用实战(06)-初识LangChain
- 2024-06-19EntBot.ai: AI Website Chatbot for Product Guides and Development Doc
- 2024-06-17zero-shot-learning-definition-examples-comparison
- 2024-06-06Package Easy(基于 NSIS 的打包exe安装包工具)使用方法-icode9专业技术文章分享
- 2024-06-06基于 casdoor 的 ELK 开源登录认证解决方案: elk-auth-casdoor-icode9专业技术文章分享
- 2024-05-29Elasticsearch慢查询日志配置
- 2024-05-29揭秘华为如此多成功项目的产品关键——Charter模板