线程执行完入口函数,也会退出,在为一个线程创建一个std::thread对象后,需要等待这个线程结束。
线程在std::thread对象创建时启动
构造std::thread对象,std::thread可以用可调用类型来构造: std::thread mythread(f) //会用f的构造函数去初始化mythread,替换默认的构造函数
std::thread mythread{f} //使用花括号更为保险,能避免语法解析,当传入的是一个临时变量,编译器会将其解析为
class background_task
{
public:
void operator()() const
{
do_something();
do_something_else();
}
};
background_task f;
std::thread my_thread(f);
函数声明,而不是类型对象的定义
启动线程后:有两种:①:等待线程结束——加入式 命名对象.join()
②:让其自主运行——分离式 命名对象.detach()
针对线程还没结束,函数以及退出的时候,这时线程还持有函数局部变量的指针或引用;要使线程函数的功能齐全,将数据复制到线程中,而非复制到共享数据中
调用join():是简单粗暴的等待线程完成,还清理了线程相关的存储部分,这样std::thread对象将不再与已经完成的线程有任何关联,意味着一个线程只能使用一个join()
如果不想等待线程结束,可以分离线程,但是线程分离,就打破了线程与std::thread对象的联系,不可能有std::therad对象能引用它,分离线程在后台运行不能被加入
分离线程:没有任何显式的用户接口,在后台运行
不能对没有执行线程的std::thread对象使用detach(),也是join的使用条件,当std::thread对象使用命名对象.joinable返回true,可以使用命名对象.detach()
向线程函数传递参数:
向std::thread构造函数中的可调用对象,或函数传递一个参数很简单,注意:即使参数是引用形式,也可以在新线程中访问
void f(int i, std::string const& s);
std::thread t(f, 3, "hello");
传参数时,有时候会存在类型的隐式转换,函数有可能在转换成功之前奔溃,需要在构造前,即传参时做类型转换
void f(int i,std::string const& s);
void not_oops(int some_param)
{
char buffer[1024];
sprintf(buffer,"%i",some_param);
std::thread t(f,3,std::string(buffer)); // 使用std::string(传参时做的类型转换),避免悬垂指针 ,悬垂指针:指向曾经的对象,但该对象已经不再存在了; 野指针:没有被正确初始化,于是指向一个随机的内存地址
t.detach();
}
线程的构造函数盲目的拷贝已提供的变量,构造函数无视原函数期待的函数类型,
当向线程中的对象传递函数且该函数本身包含引用,需要使用std::ref()将参数转换成引用的形式
void update_data_for_widget(widget_id w,widget_data& data); // 1
void oops_again(widget_id w)
{
widget_data data;
std::thread t(update_data_for_widget,w,std::ref(data)); // 2
display_status();
t.join();
process_widget_data(data); // 3
}
上面讲的都是传函数,下面传自己定义的类型,对象:
class X
{
public:
void do_lengthy_work();
};
X my_x;
std::thread t(&X::do_lengthy_work,&my_x);
//初始化时,同时传入所需参数
class X
{
public:
void do_lengthy_work(int);
};
X my_x;
int num(0);
std::thread t(&X::do_lengthy_work, &my_x, num); //std::thread构造函数的第三个参数是成员函数的第一个参数
上式中提供的参数可以移动,但是不能拷贝
每个实例(一个命名对象就是一个实例)负责管理一个执行线程,执行线程的所有权可以在多个std::thread实例中互相转移,这时依赖于std::thread实例的可移动且不可复制性
不可复制性:保证了在同一时间点,一个std::thread只能关联一个执行线程
可移动性:使程序员自己决定,哪个实例拥有实际执行线程的所有权
void some_function();
void some_other_function();
std::thread t1(some_function); // 1 执行实例化的t1
std::thread t2=std::move(t1); // 2 将t1转移到t2 ,t1已经和执行线程没什么关联了
t1=std::thread(some_other_function); // 3 移动操作将会隐式调用,因为所有者是一个临时对象
std::thread t3; // 4 t3使用默认方式创建,与任何执行线程都没有关联
t3=std::move(t2); // 5 将与t2关联线程的所有权转移到t3,因为t2是一个命名对象,需要显示调用std::move
t1=std::move(t3); // 6 赋值操作将使程序崩溃,因为t1已经有一个关联的线程,不能通过赋一个新值给std::thread对象的方式来“丢弃一个线程”
当一个线程没有命名对象时,其所有者是个临时对象
当线程的所有者是个临时对象时,移动操作将会隐式调用
当线程的所有者是个命名对象时,移动操作要显式调用
当命名对象有一个关联线程时,不能通过赋值操作给它一个新线程,不允许通过赋一个新值给std::thread对象的方式来丢弃一个线程