三、线程传参
一、thread线程对象的参数详解
1、线程函数参数是值传递
void mythread(int i){//如果不对i修改,可以改成const int i cout<<&i<<endl; } int main(){ int a=1; thread thread1(mythread,a);//第一个参数是线程函数名,后面的是线程函数的参数,值传递 thread1.join();//使用detach不会报错 cout<<&a<<endl; }
将a传入后,线程入口函数采用值传递方式先将a拷贝得到副本(即i)。所以此时的 i 和 a 的地址不同。
值传递时,使用detach()也不会出问题。
2、线程函数引用传递参数
当线程入口函数的参数列表中有引用,这个时候很多问题。
(1)构造thread对象时直接使用主线程中的对象
定义线程函数时引用的参数必须加const!因此只能只读。
int mythread(const int& i){//不加const会报错! cout<<"thread:"<<&i<<endl; return 0; } int main(){ int a=1; thread thread1(mythread,a); thread1.join();//使用detach也不会出问题 cout<<&a<<endl; return 0; }
此时i的地址和a的地址并不相同。虽然线程函数参数是引用,但构建thread1时还是拷贝出了一个a副本,线程函数引用的是副本。
(2)真正使用引用的方法:ref函数
这时在定义线程函数时,参数前的const可以去掉。
int mythread(int& i){//const去掉 i+=1; cout<<"thread:"<<&i<<endl; cout<<"i= "<<i<<endl; return 0; } int main(){ int a=1; thread thread1(mythread,ref(a));//真正意义上的引用 thread1.join();//detach()也不会出错 cout<<"main:"<<a<<endl; cout<<"main地址:"<<&a<<endl; return 0; }
3、传递字符串
int mythread(char* buf){ // cout<<buf<<endl; // cout<<&buf<<endl; cout<<&buf<<endl; cout<<"thread end"<<endl; return 0; } int main(){ char buf[]="i love china"; thread thread1(mythread,buf);//数组是指针传递,传的是第0个元素的地址。 thread1.join(); cout<<"main"<<endl; return 0; }
字符数组是数组,传递属于指针传递,数组传递的是第0个元素的地址,万一主线程执行完,buf回收,而子线程用到了buf[888]类似于这样的地址,那么就相当于指向了一个回收的内存地址,野指针现象发生。!!
如果在定义线程入口函数时,隐式转换成string。存在的问题是,万一主线程执行完了,子线程还没转换完,还是会出现野指针的现象,程序还是会报错。
因此对于字符串、类对象可以在构造thread对象时,构建临时对象即可:
class A{ public: int& m_i; A(int& i):m_i(i){} A(const A& a):m_i(a.m_i){cout<<"拷贝构造函数执行"<<endl;} ~A(){cout<<"析构函数执行"<<endl;} } void mythread(string& buf,const A& a){//引用传递 cout<<"thread"<<endl; } int main(){ int a=9; char buf[]=="ilovechina" thread thread1(mythread,string(buf),A(a));//构造两个临时对象 thread1.detach();//join、detach都没问题 cout<<"main end"<<endl; return 0; }
4、智能指针
#include <iostream> #include <thread> //线程 using namespace std; void prt(unique_ptr<int> p){ cout<<"子线程开始了"<<endl; } int main(){ unique_ptr<int> myptr(new int(100)); std::thread myobj(prt,std::move(myptr));//move将myptr给了临时的指针变量,之后myptr就没了, //如果用detach,万一myprt指向的内存回收了,那么子线程的这个指针就指向了一个被回收的内存地址,就会出错,所以这个时候不能用detach //只能用join myobj.join(); cout<<"main end"<<endl; return 0; }
5、成员函数指针做线程参数
#include <iostream> #include <thread> //线程 using namespace std; class A{ public: int m_i; A(int a):m_i(a){cout << "A(int)构造函数执行了"<<"类对象的地址是:"<<this<<endl;} A(const A &a):m_i(a.m_i){cout<<"A(const)的拷贝构造函数执行了"<<endl;} ~A(){cout<<"A的析构函数执行"<<endl;} void thread_work(int num){ //线程执行入口 cout << "子线程开始"<<endl; } }; int main(){ A my(10); std::thread myobj(&A::thread_work,my,15);//注意成员函数参数写在后面,这时候的my已经是一个复制后的临时变量 //thread myobj(&A::thread_work,&my,15)==(&A::thread_work,std::ref(my),15);这个时候就么有用拷贝构造函数,没有复制,用的就是my自己,此时不能用detach myobj.join(); cout<<"main end"<<endl; return 0; }
二、线程id
线程id可以用c++获取到:std::this_thread::get_id()来获取。
//在各自线程函数中: cout << std::this_thread::get_id()<<endl;
坑以及注意事项
传递指针时是浅拷贝,即使线程函数定义时是引用,引用的只是副本而已。
主线程与子线程指针指向的同一内存,这样的情况很危险,多个指针指向同一个内存地址,子线程对这个内存对象的更改会影响到主线程中其他指针的使用。
在 C++中,数组永远不会按值传递,它是传递第一个元素,准确地说是第 0个 的指针。
c++数组没有引用的规则,定义函数时不能用引用传递数组。string是类,string类对象可以被引用。