一旦开始了线程,需要显示决定要等待线程函数完成或分离它自行完成。如果detach()线程不等待,你要确保通过线程访问的数据是有效的,直至该线程完成为止,例如线程函数持有局部变量的指针或引用,且当主函数退出的时候线程未完成,就会出错,线程函数就会访问一个已被销毁的变量,解决方法是数据私有化。join()背后的含义有两层,一是等待子线程执行完毕,避免主线程先完成,从而导致子线程终止,二是join()会清理与子线程相关联的存储器,这样std::thread对象不再与子线程相关联,这就意味着你只能对一个给定线程调用一次join(),一旦调用了join(),此std::thread对象不再是可连接的,并且joinable()将返回false。
struct func
{
int &i;
func(int& i_):i(i_){}
void operator()
{
for(unsigned int j=0;j<1000;j++)
do_something(i); //可能会访问悬空引用
}
};
void main()
{
int some_local_state=0;
func my_func(some_local_state);
std::thread my_thread(my_func);
my_thread.detach(); //不等待线程完成,可能新的线程仍在运行
}
如果打算等待该线程,就需要仔细选择在代码那个位置调用join(),如果在线程开始调用后,join()之前引发了异常,对join()的调用就会跳过。为了避免程序在引发异常的时候被终止,还需要在存在异常时调用join()。
struct func;
void main()
{
int some_local_state=0;
func my_func(some_local_state);
std::thread t(my_func);
try
{
do_somthing_in_current_thread();
}
catch(...)
{
t.join();
throw;
}
t.join();
}
使用try/catch块很罗嗦,而且容易将作用域弄乱,所以并不是一个理想方案。如果确保线程必须在函数退出前完成是很重要的,无论是因为它具有对其他局部变量的引用还是任何其他原因,那么确保这是所有可能退出路径的情况很重要,无论正常还是异常,希望提供一个简单明了的机制。这种做法之一是使用标准的资源获取初始化,并提供一个类,在它析构函数中进行join()。
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;
void main()
{
int some_local_state=0;
func my_func(some_local_state);
std::thread my_thread(my_func);
thread_guard g(t);
do_something_in_current_thread(); //4
}
在主线程执行到末尾4时,局部变量会按照构造函数的逆序销毁,因此g首先被销毁,并且析构函数2中线程被结合,即便4中引发异常的情况下也会发生。拷贝构造函数和拷贝赋值运算法被禁止,以确保它们不会被编译器自动生成,复制或赋值这样一个对象时比较危险的,因为它们可能比它要结合的线程的作用域存在的更久。