detach()的作用是将子线程和主线程的关联分离,也就是说detach()后子线程在后台独立继续运行,主线程无法再取得子线程的控制权,即使主线程结束,子线程未执行也不会结束。当主线程结束时,由运行时库负责清理与子线程相关的资源。实际应用如让一个文字处理应用同时编辑多个文档,让每个文档处理窗口拥有自己的线程,每个线程运行同样的代码,并隔离不同窗口处理的数据。
1 #include <thread> 2 #include <iostream> 3 4 using namespace std; 5 6 void func() 7 { 8 cout << "子线程func开始执行!" << endl; 9 //do something 10 cout << "子线程func执行结束!" << endl; 11 } 12 13 int main() 14 { 15 cout << "主线程main开始执行!" << endl; 16 thread t(func); 17 t.detach(); 18 cout << "主线程main执行结束!" << endl; 19 return 0; 20 }
注意
detach()同时也带来了一些问题,如子线程要访问主线中的对象,而主线中的对象又因为主线程结束而被销毁时,会导致程序崩溃。所以传递参数时需要注意一些陷阱。关于参数传递:
1、访问主线程对象以及指针问题
2、构造线程时隐式转换问题,子线程可以还来不及转换,主线程对象就销毁了,解决方法是构造线程时,构造一个临时对象传入。
线程可以共享进程的内存空间,线程拥有自己独立内存。
关于参数的传递,std::thread的构造函数只会单纯的复制传入的变量,特别需要注意的是传递引用时,传入的是值的副本,也就是说子线程中的修改影响不了主线程中的值。
值传递
主线程中的值,被拷贝一份传到了子线程中。
1 #include <iostream> 2 #include <thread> 3 4 using namespace std; 5 6 void test(int ti, int tj) 7 { 8 cout << "子线程开始" << endl; 9 //ti的内存地址0x0055f69c {4},tj的内存地址0x0055f6a0 {5} 10 cout << ti << " " << tj << endl; 11 cout << "子线程结束" << endl; 12 return; 13 } 14 15 16 int main() 17 { 18 cout << "主线程开始" << endl; 19 //i的内存地址0x001efdfc {4},j的内存地址0x001efdf0 {5} 20 int i = 4, j = 5; 21 thread t(test, i, j); 22 t.join(); 23 cout << "主线程结束!" << endl; 24 return 0; 25 }
传引用
从下面的运行结果,可以看出,即使是用引用来接收传的值,也是会将其拷贝一份到子线程的独立内存中,这一点与我们编写普通程序时不同。这是因为线程的创建属于函数式编程,所以为了传引用C++中才引入了std::ref()。关于std::ref()。
1 #include <iostream> 2 #include <thread> 3 4 using namespace std; 5 6 class A{ 7 public: 8 int ai; 9 A (int i): ai(i) { } 10 }; 11 12 //这种情况必须在引用前加const,否则会出错。目前本人的觉得可能是因为临时对象具有常性 13 void test(const int &ti, const A &t) 14 { 15 cout << "子线程开始" << endl; 16 //ti的内存地址0x0126d2ec {4},t.ai的内存地址0x0126d2e8 {ai=5 } 17 cout << ti << " " << t.ai << endl; 18 cout << "子线程结束" << endl; 19 return; 20 } 21 22 23 int main() 24 { 25 cout << "主线程开始" << endl; 26 //i的内存地址0x010ff834 {4},a的内存地址0x010ff828 {ai=5 } 27 int i = 4; 28 A a = A(5); 29 thread t(test, i, a); 30 t.join(); 31 cout << "主线程结束!" << endl; 32 return 0; 33 }
那么如果我们真的需要像一般程序那样传递引用呢,即在子线程中的修改能够反映到主线程中。此时需要使用std::ref()。但是注意如果我们会在子线中改变它,此时用于接收ref()的那个参数前不能加const。关于C++多线程中的参数传引用问题,我目前只是记住了这个现象,关于原理还需后期研究源码继续学习。
1 #include <iostream> 2 #include <thread> 3 4 using namespace std; 5 6 class A { 7 public: 8 int ai; 9 A(int i) : ai(i) { } 10 }; 11 12 //接收ref()的那个参数前不能加const,因为我们会改变那个值 13 void test(int& ti, const A& t) 14 { 15 cout << "子线程开始" << endl; 16 cout << ti << " " << t.ai << endl; 17 ti++; 18 cout << "子线程结束" << endl; 19 return; 20 } 21 22 23 int main() 24 { 25 cout << "主线程开始" << endl; 26 int i = 4; 27 A a = A(5); 28 thread t(test, ref(i), a); 29 t.join(); 30 cout << "i改变:" << i << endl; 31 cout << "主线程结束!" << endl; 32 return 0; 33 }
传入类对象时,使用引用来接收比用值接收更高效。
1 #include <iostream> 2 #include <thread> 3 4 using namespace std; 5 6 class A { 7 public: 8 int ai; 9 A (int i) : ai(i) 10 { 11 cout << "构造" << this << endl; 12 } 13 14 A (const A& a) :ai(a.ai) { 15 cout << "拷贝构造" << this << endl; 16 } 17 18 ~A() 19 { 20 cout << "析构" << this << endl; 21 } 22 }; 23 24 //void test(const A a) 25 void test(const A& a) 26 { 27 cout << "子线程开始" << endl; 28 cout << "子线程结束" << endl; 29 return; 30 } 31 32 33 int main() 34 { 35 cout << "主线程开始" << endl; 36 int i = 4; 37 thread t(test, A(i)); 38 t.join(); 39 cout << "主线程结束!" << endl; 40 return 0; 41 }
传指针
从下面的运行结果,可以看出,主线程和子线程中的指针都是指向同一块内存。所以在这种情况下会有一个陷阱,如果使用detach(),则当主线程崩溃或者正常结束后,该块内存被回收,若此时子线程没有结束,那么子线程中指针的访问将未定义,程序会出错。
1 #include <iostream> 2 #include <thread> 3 4 using namespace std; 5 6 7 void test(char *p) 8 { 9 cout << "子线程开始" << endl; 10 //0x004ffeb4 "hello" 11 cout << p << endl; 12 cout << "子线程结束" << endl; 13 return; 14 } 15 16 17 int main() 18 { 19 cout << "主线程开始" << endl; 20 //0x004ffeb4 "hello" 21 char s[] = "hello"; 22 thread t(test, s); 23 t.join(); 24 cout << "主线程结束!" << endl; 25 return 0; 26 }
传临时对象
用临时变量作为实参时,会更高效,由于临时变量会隐式自动进行移动操作,这就减少了整体构造函数的调用次数。而一个命名变量的移动操作就需要std::move()。
1 #include <iostream> 2 #include <thread> 3 4 using namespace std; 5 6 class A { 7 public: 8 int ai; 9 A (int i) : ai(i) 10 { 11 cout << "构造" << this << endl; 12 } 13 14 A (const A& a) :ai(a.ai) { 15 cout << "拷贝构造" << this << endl; 16 } 17 18 ~A() 19 { 20 cout << "析构" << this << endl; 21 } 22 }; 23 24 void test(const A& a) 25 { 26 cout << "子线程开始" << endl; 27 cout << "子线程结束" << endl; 28 return; 29 } 30 31 32 int main() 33 { 34 cout << "主线程开始" << endl; 35 int i = 4; 36 thread t(test, A(i)); 37 t.join(); 38 cout << "主线程结束!" << endl; 39 return 0; 40 }
总结
1、使用引用和指针是要注意;
2、对于内置简单类型,建议传值;
3、对于类对象,建议使用引用来接收,以为使用引用会只会构造两次,而传值会构造三次;
4、在detach下要避免隐式转换,因为此时子线程可能还来不及转换主线程就结束了,应该在构造线程时,用参数构造一个临时对象传入。
5、实际上,也并不是说不能用detach(),有时候要在线程中和主线程共用某些地址,在我们前面的博客中,我们就在线程函数中传了指针,在线程函数中读取指针指向的位置的数据。但是,我们必须在主线程中的析构函数或者退出函数中将传出去的那些指针都赋为null,并在线程函数中对传进来的指针做出判断,判定是否为null,然后再执行相应的逻辑(潜在的风险是该地址在主线程中赋值为null,但是后面又起来一个程序使用了该地址并赋值)。或者加上flag控制开关,在主线程退出时将flag赋为false,在线程函数中获取flag然后执行逻辑。
后面我会写程序再验证上述观点。
参考出处:https://www.cnblogs.com/chen-cs/p/13056703.html
今天的文章c++多线程实例_c++多线程实现的四种方式分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/84058.html