四、线程传参详解
本节主要详细记录std::thread
的构造函数,就是函数的形参列表。
1、传递临时对象作为线程参数
thread类中的detach()方法是一个大坑,它将简单的东西复杂化,需要谨慎处理!
1 2 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
的可调用对象中,不推荐使用引用传递(引用的真实含义被覆盖了),同时绝对不可以用指针!如何安全的将字符串传递进来:
1 2 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信息我们就可以追踪临时对象是在哪一个线程中被构造出来的:
1 2 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()
函数就可以实现引用的真正用处了:
1 2 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、任意一个成员函数作为线程函数
调用类中的任意一个成员函数,可以用以下的写法来实现:
1 2 3 4 5
| TestThread aaa(20); thread mytobj1(&ThreadTest::thread_work, aaa, 15);
myobj1.join();
|