四、线程传参详解
本节主要详细记录std::thread的构造函数,就是函数的形参列表。
1、传递临时对象作为线程参数
thread类中的detach()方法是一个大坑,它将简单的东西复杂化,需要谨慎处理!
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 
 | #include <iostream>#include <thread>
 
 using namespace std;
 
 void myprint(const int& i, char* pmybuf) {
 cout << i << endl;
 cout << pmybuf << endl;
 }
 
 int main()
 {
 int mvar = 1;
 int &mvary = mvar;
 char mybuf[] = "This is a test";
 
 thread mytobj(myprint, mvar, mybuf);
 mytobj.join();
 
 cout << "I Love China" << endl;
 return 0;
 }
 
 | 
所以传给thread的可调用对象中,不推荐使用引用传递(引用的真实含义被覆盖了),同时绝对不可以用指针!如何安全的将字符串传递进来:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | void myprint(const int& i, const string& pmybuf) { cout << i << endl;
 cout << pmybuf << endl;
 }
 int main()
 {
 int mvar = 1;
 int &mvary = mvar;
 char mybuf[] = "This is a test";
 thread mytobj(myprint, mvar, mybuf);
 
 mytobj.detach();
 
 cout << "I Love China" << endl;
 return 0;
 }
 
 | 
在创建线程的同时构造临时对象的方法传递参数是可行的,他可以保证在主线程结束前将myprint()的第二个参数构造出来,直接使用隐式类型转换再detach()则不能保证。上述的程序会先调用string中的构造函数创建一个临时对象,接着并不是引用这个临时对象,而是调用copy构造函数又复制出来一个对象给子线程使用!
对于使用detach()的情况:
- 若果传递内置类型的变量,建议都是按照值传递,不推荐使用引用,坚决不能用指针。
- 如果传递类对象,避免使用隐式类型转换。全部都在创建线程这一行创建出临时对象来,并且在函数参数中使用引用来接收参数。
- 除非万不得已!是使用join()就不会出现局部变量失效导致线程对内存非法访问的情形。
2、线程ID
每个线程,不管是主线程还是子线程实际上都对应了一个数字,可以使用C++标准库中的std::this_thread::get_id()来获取。利用这个ID信息我们就可以追踪临时对象是在哪一个线程中被构造出来的:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 
 | #include "pch.h"#include <iostream>
 #include <thread>
 using namespace std;
 class TestThread {
 public:
 TestThread(int i) :data_(i) {
 cout << "构造函数被执行" << this <<"创建此对象的ID是:"<<std::this_thread::get_id()<< endl;
 }
 TestThread(const TestThread& testthread) :data_(testthread.data_) {
 cout << "Copy构造函数被执行" << this <<"执行copy操作的线程ID是:"<<std::this_thread::get_id()<< endl;
 }
 ~TestThread() {
 cout << "析构函数被执行" << this <<"执行析构的线程ID是:"<<std::this_thread::get_id()<< endl;
 }
 void thread_work(int num){
 cout<<"类TestThread中的线程函数"<<this<<"所在线程ID"<<std::this_thread::get_id()<<endl;
 }
 public:
 int data_;
 };
 void MyTestThread(const TestThread& testthread) {
 cout << "子线程的ID是:" <<std::this_thread::get_id()<< endl;
 }
 int main()
 {
 cout << "主线程的ID是:" << std::this_thread::get_id() << endl;
 int mvar = 1;
 
 thread mytobj1(MyTestThread,TestThread(mvar));
 mytobj1.join();
 return 0;
 }
 
 | 
- 当使用隐式类型转换时,临时对象是在子线程中被构建的,这就会导致detach()出现问题。(主线程执行完,子线程无法构造了)
- 当创建线程使用临时对象时,临时对象是在主线程中被构建出来的,这样确保使用子线程不会出问题。
3、std::ref()函数
前边说过,尽管函数void myprint(const int& i, const string& pmybuf)是按照引用的方式传递的,但是编译器调用copy构造函数又创建了一个对象,这就导致在子线程中对pmybuf的更改不会反馈到主线程中(const 限定符也不允许我们这么做,如果去掉const限定符号,编译又会出错)。
这时候使用std::ref()函数就可以实现引用的真正用处了:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | void MyTestThread(TestThread& testthread) {          cout << "子线程的ID是:" <<std::this_thread::get_id()<< endl;
 }
 int main()
 {
 cout << "主线程的ID是:" << std::this_thread::get_id() << endl;
 TestThread aaa(20);
 thread mytobj1(MyTestThread, std::ref(aaa));
 mytobj1.join();
 return 0;
 }
 
 | 
4、任意一个成员函数作为线程函数
调用类中的任意一个成员函数,可以用以下的写法来实现:
| 12
 3
 4
 5
 
 | TestThread aaa(20);thread mytobj1(&ThreadTest::thread_work, aaa, 15);
 
 
 myobj1.join();
 
 |