坑点总结
1、启动了线程,你需要明确是要等待线程结束(调用join),还是让其自主运行(调用detach)。如果std::thread对象销毁之前还没有做出决定,程序就会终止(std::thread的析构函数会调用std::terminate())。因此,即便是有异常存在,也需要确保线程能够正确的加入(joined)或分离(detached)。。需要注意的是,必须在std::thread对象销毁之前做出决定,否则你的程序将会终止(std::thread的析构函数会调用std::terminate(),这时再去决定会触发相应异常)。
如果不等待线程,就必须保证线程结束之前,可访问的数据的有效性。这不是一个新问题——单线程代码中,对象销毁之后再去访问,也会产生未定义行为——不过,线程的生命周期增加了这个问题发生的几率。
这种情况很可能发生在线程还没结束,函数已经退出的时候,这时线程函数还持有函数局部变量的指针或引用。下面的清单中就展示了这样的一种情况。
class func1
{
public:
func1(int &a) : i(a) {
}
void operator() ()
{
//在这里调用i变量
}
private:
int &i;
};
void oops()
{
int local_variable = 0;
func1 my_func(local_variable);
std::thread my_thread(my_func);
my_thread.detach(); //这里将线程分离,可能local_vriable变量已经销毁该线程还在运行
}
- 这个例子中,已经决定不等待线程结束(使用了detach() ),所以当oops()函数执行完成时,新线程中的函数可能还在运行。如果线程还在运行,它就会访问已经销毁的变量。如同一个单线程程序——允许在函数完成后继续持有局部变量的指针或引用;当然,这从来就不是一个好主意——这种情况发生时,错误并不明显,且会使多线程更容易出错。
- 处理这种情况的常规方法:使线程函数的功能齐全,将数据复制到线程中,而非复制到共享数据中。如果使用一个可调用的对象作为线程函数,这个对象就会复制到线程中,而后原始对象就会立即销毁。但对于对象中包含的指针和引用还需谨慎,