线程在std::thread对象创建的时候启动,而类似Java或Qt的线程,线程在调用start()函数时启动
使用有调用运算符的类实例作为thread实参:
class background_task
{
public:
void operator()() const
{
do_something();
do_something_else();
}
};
background_task f;
std::thread my_thread(f);
函数对象会被复制到线程的存储空间中,在线程的内存空间执行和调用
thread构造语法:
1.
- std::thread my_thread(background_task()); //错误,此处声明了一个返回值是thread,形参是个函数指针,函数指针指向返回值是空,参数是空的函数
- std::thread my_thread((background_task())); //对
- std::thread my_thread{background_task()}; //对
2.std::function f = []() {...} std::thread my_thread(f); //这种方法的好处是线程的启动可以延迟
3.使用异步API std::async或boost::async代替thread
thread t创建后必须要在某处调用t.join()(加入)或t.detach(分离)
thread必须在销毁之前调用join或detach,否则程序会终止(thread在析构的时候会调用std::terminate)
根据文档,在异常处理失败时(此处原因是joinable线程销毁了),C++ runtime会调用std::terminate,而std::terminate会调用std::abort,std::abort默认的行为是终止程序,代码不会继续往下执行
一个很容易犯的错误就是线程分离后,访问了已经销毁的局部变量的指针或引用:
struct func
{
int& i;
func(int& i_) : i(i_) {}
void operator() ()
{
for (unsigned j=0 ; j<1000000 ; ++j)
{
do_something(i); // 1. 潜在访问隐患:悬空引用
}
}
};
void oops()
{
int some_local_state=0;
func my_func(some_local_state);
std::thread my_thread(my_func);
my_thread.detach(); // 2. 不等待线程结束
} // 3. 新线程可能还在运行
所以当线程参数包含指针或引用的时候一定要小心
目前有两种解决方案:
1.主线程调用t.join等待子线程结束,同步等待
2.传参的时候我个人推荐使用智能指针shared_ptr,不需要关心参数资源释放问题
t.join只能调用一次,所以可以配合t.joinable使用
需要注意的一点是在不使用异常捕获机制的前提下:在线程创建后,t.join调用之前不能抛出异常,否则程序会终止
我认为如果整体架构下不使用异常捕获机制try-catch,就必须由自己保证没有异常
如果使用try-catch,那么可以在catch里面调用t.join保证程序继续执行
更聪明的做法是使用“资源获取即初始化方式”(RAII,Resource Acquisition Is Initialization):
class thread_guard
{
std::thread& t;
public:
explicit thread_guard(std::thread& t_):
t(t_)
{}
~thread_guard()
{
if(t.joinable()) // 1
{
t.join(); // 2
}
}
thread_guard(thread_guard const&)=delete; // 3
thread_guard& operator=(thread_guard const&)=delete;
};
struct func; // 定义在清单2.1中
void f()
{
int some_local_state=0;
func my_func(some_local_state);
std::thread t(my_func);
thread_guard g(t);
do_something_in_current_thread();
} // 4
RAII的例子还有智能指针,lock_guard
为什么要用RAII?
C++保证栈上的对象即使异常也能能够正常被析构,也就是说RAII类是异常安全的
向线程函数传递参数:
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,data); // 2 期望修改data
display_status();
t.join();
process_widget_data(data); // 3 data还是1中传入的data
}
上述代码期望传递引用到函数参数里,但是thread构造的时候对参数进行的是拷贝,
即使传的是引用,也会对引用数据进行拷贝,要想达到在线程函数内部引用数据的目的,需要使用std::ref/std::cref
参数是先传给thread,再由thread传给线程函数的,而thread会默认拷贝参数
这个错还是很容易犯的,当我们需要使用参数保证线程间同步时,就会用到std::ref
这里还有一点要注意的,传给thread的类对象必须是可拷贝的,也就是具有拷贝构造函数,atomic_bool就没有拷贝构造