一、detach()大坑
上一篇随笔(二)中提到detach()是用来分离主线程和子线程的,那么需要考虑一个问题,就是如果主线程跑完了,主线程中定义的变量就会被销毁(释放内存),这时回收变量仍作为参数传入子线程,那么就会出现问题,下面用一个例子详细说明。
1 #include <iostream> 2 #include <string> 3 #include <thread> 4 using namespace std; 5 6 void MyThread(const int& a, char* str) 7 { 8 cout << a << endl; 9 cout << str << endl; 10 } 11 int main() 12 { 13 14 int n = 20; 15 char str_m[] = "hello wrold"; 16 thread MyThreadObj(MyThread, n, str_m); 17 if (MyThreadObj.joinable()) 18 { 19 MyThreadObj.detach(); 20 } 21 cout << "main_thread" << endl; 22 system("pause"); 23 return 0; 24 }
由监视图可知,实参n和形参a的地址并不同,所以实际是值传递,并因此最好不要用引用,直接用值传递就行了。
主线程的str_m和str的地址却相同,那么当子线程和主线程分离时,就会出现问题。这里讲一个改进的方法,把形参char* str改成const string& str,即把传进来的参数隐式地转换成一个(构造了)string对象,会发现主线程的str_m和子线程形参str的地址是不同的,但实际上问题还是存在,如果在在传递主线程的参数str_m前,str_m就被回收了,一个被回收的变量作为参数结果可想而知。所以还要再改多一步,就是在main()中实参str_m改成string(str_m),先构造一个string对象,再将这个对象作为参数传入子线程,这时又会有疑问,难道不会出现在string(str_m)前str_m就被系统回收了吗?答案是不会的,测试方法这里不细说了。
下面是改进后的代码
1 #include <iostream> 2 #include <string> 3 #include <thread> 4 using namespace std; 5 6 void MyThread(int a, const string& str) 7 { 8 cout << a << endl; 9 cout << str << endl; 10 } 11 int main() 12 { 13 14 int n = 20; 15 char str_m[] = "hello wrold"; 16 thread MyThreadObj(MyThread, n, string(str_m)); 17 if (MyThreadObj.joinable()) 18 { 19 MyThreadObj.detach(); 20 } 21 cout << "main_thread" << endl; 22 system("pause"); 23 return 0; 24 }
二、std::this_thread::get_id()
线程id,每个线程都有自己的线程id,各个id都不同,获取线程id方法为std::this_thread::get_id()
1 #include <iostream> 2 #include <string> 3 #include <thread> 4 using namespace std; 5 class CA 6 { 7 public: 8 int a; 9 CA(int m) :a(m) 10 { 11 cout << "构造函数执行 " << endl; 12 } 13 CA(const CA&m) :a(m.a) 14 { 15 cout << "拷贝构造函数执行 " << endl; 16 } 17 }; 18 void MyThread(const CA&cc) 19 { 20 cout << "子线程id:" << this_thread::get_id() << endl; 21 cout << cc.a << endl; 22 } 23 int main() 24 { 25 cout << "主线程id:" << this_thread::get_id() << endl; 26 CA ca(10); 27 thread mythreadObj(MyThread,ca); 28 mythreadObj.join(); 29 system("pause"); 30 return 0; 31 }
三、std::ref()
上述代码运行可知,如果向子线程传入一个对象,会调用拷贝构造函数,这时用detach()是安全的,但是如果想要通过子线程来修改主线程对象的数据是不允许的,可以把18行的const去掉试试,是会报错的,那么可以用std::ref()来使这个对象得以修改。(当然,18行处也可以改成void MyThread(CA cc),这样也是可以修改,但是修改的只是拷贝的对象,对主线程的对象没有影响,而且会调用两次拷贝构造函数,浪费内存)
1 #include <iostream> 2 #include <string> 3 #include <thread> 4 using namespace std; 5 class CA 6 { 7 public: 8 int a; 9 CA(int m) :a(m) 10 { 11 cout << "构造函数执行 " << endl; 12 } 13 CA(const CA&m) :a(m.a) 14 { 15 cout << "拷贝构造函数执行 " << endl; 16 } 17 }; 18 void MyThread(CA& cc) 19 { 20 cc.a++; 21 cout << "子线程id:" << this_thread::get_id() << endl; 22 cout << "my_thread " <<"ca.a:" << cc.a << endl; 23 } 24 int main() 25 { 26 cout << "主线程id:" << this_thread::get_id() << endl; 27 CA ca(10); 28 thread mythreadObj(MyThread, ref(ca)); 29 cout << "main_thread " << "ca.a:" << ca.a << endl; 30 mythreadObj.join(); 31 system("pause"); 32 return 0; 33 }
由结果可知,并不会调用拷贝构造函数,在子线程中操作的是主线程中的对象。这时需要注意了,如果用detach(),就不安全了。还是那个问题,对已经释放的变量进行非法操作必定带来隐患。