多线程
注 : 看着视频教程的手写笔记 , 如有错误 , 评论更正
#include<iostream>
#include<cstdio>
#include<thread>
#include<stdlib.h>
using namespace std;
void myprint(){
cout<<"子线程开始了"<<endl;
cout<<"子线程结束了"<<endl;
}
int main(){
cout<<"主线程开始啦"<<endl;
thread mytobj(myprint);//创建了线程,并开始执行
//mytobj.join();//主线程阻塞到这里,等待该子线程结束才继续执行
//mytobj.detach();
cout<<"主线程结束啦"<<endl;
return 0;
}
thread
void 线程函数名(){}
thread 线程对象名(线程函数名);//创建了线程,并开始执行
join()
线程对象.join();//主线程阻塞到这里,等待该子线程结束才继续执行
一个书写良好的程序,应该是主线程等待子线程执行完毕后,自己才能退出
detach()
线程对象.detach();//主线程与子线程分离
detach()
: 分离,主线程不与子线程汇合了 , 主线程不必等待子线程结束一旦
detach()
之后,与这个主线程关联的thread对象就会失去与这个主线程的关联关系 , 此时这个子线程就会驻留在后台运行相当于这个子线程被c++运行时库接管,当该子线程运行结束,有运行时库清理该线程的相关资源(守护线程)
一旦
detach
了,就不能再用join了,系统会报异常应用较少
joinable()
if(线程对象.joinable()){
线程对象.join();//可以
//线程对象.detach();
}
判断是否可以成功使用join()或detach()的
返回 true(就可以
join
或detach
) 或者flase
类对象作为线程函数
class A{
public:
void operator()(){
cout<<"子线程开始了"<<endl;
cout<<"子线程结束了"<<endl;
}
};
int main(){
A a;
thread myjob(a);
thread myjob( ref(a) );//传引用.不能用detach
myjob.join();
return 0;
}
类对象使用
detach()
可能会出bug,比如子线程引用了主线程的某些变量,当主线程结束后,资源就销毁了,但子线程还继续调用主线程资源,就会有bug产生,指针也很可能产生问题当调用
detach()
, a对象便被复制到(拷贝构造函数)了子线程当中去 , 当主线程结束,主线程中的a便被销毁了,但子线程中的复制的a依旧存在,join()
类似子线程执行完了 , 子线程中的对象便执行析构函数,释放内存
lambda表达式
auto mylamthread = [] {
cout<<"我的线程开始执行了"<<endl;
cout<<"我的线程执行结束了"<<endl;
};
thread myjob(mylamthread);
myjob.join();
//myjob.detach();
传参
void print(int t,char s ){
cout<<t<<endl;
cout<<s<<endl;
}
thread myjob(print,t,s);
void print(const int &t,char *s ){ cout<<t<<endl; cout<<s<<endl;}int main(){ int t=1; int &mt=t; char s[]="i love the world"; thread myjob(print,t,s); myjob.detach(); cout<<"主线程结束"<<endl; return 0;}
用
detach()
时 , 主线程不再等待子线程,当主线程结束 , 资源被释放 , 子线程中的指针指向的内存没有了 , 会出bug , 但是引用不会 , 因为这里的引用都是假引用 , 照样回创建变量.
假设我们将这里改成
void print(const int &t,const string &s )
还是可能会出问题,因为我们不知道s是什么时候转成string的 , 可能在main执行结束之后 , 那么就会出bug
最佳解决方案
void print(const int &t,const string &s )thread myjob(print,t,string(s));
这样就可以了 , 这样就是说我们先进行了类型转换,然后再调用进行thread的构造函数,就即时地将这里的参数给了一份给子线程
这里的调用情况为 :
1,调用了string的转换构造函数,生成临时对象
2,thread构造函数
3,调用拷贝构造函数生成子线程中的string对象
hint
若传递int这个简单类型,建议都不写引用
如果传递对象 , 避免隐式类型转换 . 全部都在创建线程这一行就构造出临时对象来 , 然后在函数参数里 , 用引用来接 , 否者会多构造出一次对象
线程id
std::this_thread::get_id()
概念 : id是一个数字 , 每个线程(不管是主线程还是子线程) 实际上都是一个数字 , 而且每个线程id都不同
线程id可以用c++标准库里的函数来获取 .
std::this_thread::
get_id()
来获取
std:ref( 参数)
void myprint( int &a )/**********************/ int x=3; thread myjob(myprint,std::ref(x));
ref
可以使线程真的引用
参数是智能指针
void myprint( unique_ptr<int> a_ptr )int main(){ unique_ptr<int> b_ptr(new int(3)); thread myjob(myprint,move( b_ptr ) ); myjob.join(); if( b_ptr==nullptr ){//智能指针,b_ptr为空 cout<<"b_ptr is empty!!!"<<endl; } return 0;}
要用
join
,不要用detach
,因为new在主线程,主线程结束new的资源就被释放了
成员函数做子线程函数
class A{public: int val; A(){} A(int item):val(item){} A(const A &a):val(a.val){cout<<"is copy !!!"<<endl;} ~A(){} void mythread(int item){ cout<<"sun_start"<<endl; cout<<"sun_end"<<endl; }};int main(){ cout<<"main_start"<<endl; A a(666); thread myjob( &A::mythread,a,19 );//这个可以用detach thread myjob( &A::mythread,ref(a),19 );//真引用,这个不能用detach thread myjob( &A::mythread,&a,19 );//等价与ref写法,但不安全 myjob.join(); cout<<"main_end"<<endl; return 0;}
只要是传值(拷贝构造) , 就可以用
detach
学习心得
可以通过打印参数地址的方法判断
detach
是否安全 , 比较参数是引用的还是再拷贝的 , 当然如果是new在函数的内存,传递时只进行了浅拷贝就不能detach
了.