线程管控
发起线程
启动线程
c++标准库启动线程需要构造std::thread对象,任何可调用对象都可以适用
// 函数调用
void doSomeWork();
std::thread mythread(doSomeWork);
// 有函数调用操作符的类
class background_task
{
public:
void operator() () const
{
doSomeThing();
doSomeThingElse();
}
};
background_task t;
std::thread my_thread(t);
// lambda表达式
std::thread my_thread([]{
doSomeThing();
doSomeThingElse();
});
启动后主线程处理
在线程结束前,我们需要保证线程中访问到的外部数据始终有效,所以通常我们要把需要的数据复制到线程内部,而不是共享。特别注意线程不要共享启动线程函数中的局部变量,除非可以保证线程在函数退出前结束
struct my_func
{
int& i_;
my_func(int& i) :i_(i) {}
void operator()()
{
do_something(i); // 这里i可能是销毁的指针
}
}
void oops()
{
int some_local_state =0;
std::thread t(my_func, some_local_state);
t.detach();
}
等待线程完成
- 上述问题的一种解决办法是,调用join
- join会阻塞主线程等待创建的线程汇合,只能调用一次,调用后joinable()将返回false
- 调用join前要注意主线程是否会因为异常抛出,对于异常处理也要调用join(),可以使用RAII的方法,使用guard处理
std::thread t(my_func);
t.join();
在后台运行线程
- 与join对应,调用detach()可以让线程在后台运行,分离后线程的运行权利控制权都交给了运行时库处理
- 对std::thread对象调用detach()后,对象不在关联线程,也不可以再汇合
对线程函数传参
- 向线程的函数或者可调用对象传参时,直接在std::thread的构造函数中添加更多参数即可
- 线程内部有存储空间,参数会按照默认方式复制到这里,然后副本会被当做临时变量,用右值的方式传给新线程上的函数或调用对象。如果参数需要类型转化,转化是在新线程开始时进行,std::thread中拷贝参数是在原线程中拷贝,复制的内容也是std::tread构造函数中原本的内容。
class MyClass
{
....
};
void func(MyClass& c)
{
...
};
int main()
{
MyClass c;
std::thread t(func, c);
t.join();
return 0;
}
- 案例无法通过编译,因为在std::thread,在构造时会原本的复制MyClass类型到线程的存储空间中,当做move_only类型,并以右值的形式传递。最终func收到的是一个右值作为参数,而func希望的是一个非const参数,所以编译失败。解决的办法可以是func改为接受一个const MyClass& 引用,或者用std::ref包装参数.
std::thread t(func, std::ref(c));