如前所述,可以通过join()来等待线程完成。但是,当程序变得复杂时,程序的出口可能不止一个,如果主线程(我觉得它是,就这样吧)打算等待子线程完成,就需要仔细选择在代码的哪个位置调用join(),避免因为程序异常,跳过了对join()的调用。
struct func { int& i; func(int& i_) :i(i_) {} void operator()() { for (unsigned j = 0; j < 1000000; j++) { do_somnething(i); // 不断对引用的访问 } } }; void f() { int some_local_state = 0; func my_func(some_local_state); std::thread t(my_func); try { do_something_in_current_thread(); } catch(...) { t.join(); // 异常中断 throw; } t.join(); // 正常退出 }
以上示例,使用了try/catch块,以确保线程t在函数f退出前结束,无论函数是正常退出还是异常中断。但是使用ry/catch很啰嗦,而且容易将作用域弄乱,所以不是一个理想的方案。可以按以下方式进行改写。
class thread_guard { std::thread& t; public: explicit thread_guard(std::thread& t_):t(t_){} ~thread_guard() { if(t.joinable()) // ① 一个给定的执行线程join()只能被调用一次,所以,首先判断线程是否已经被结合 { t.join(); // ② } } // ③ 拷贝构造函数和拷贝赋值运算符被标记为delete,以确保类对象不会被复制或拷贝,复制和拷贝这样一个对象可是很危险的 thread_guard(thread_guard const&) = delete; thread_guard& operator=(thread_guard const&) = delete; } struct func; // 定义同上 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(); } // ④
在当前线程执行到函数f末尾④时,函数内的局部对象会按照定义顺序的逆序被销毁。因此,thread_guard对象g首先被销毁。thread_guard类的析构函数内,线程t被结合②。所以,线程t执行完毕后,thread_guard对象g才会被成功销毁,g销毁后,继续逆序销毁其它局部对象,这样,避免了线程t执行完之前,some_local_state对象就被销毁了,从而引发错误。而且,这种方式,即便是当函数do_something_in_current_thread引发异常而退出的情况下也会发生上述过程。
原文:https://www.cnblogs.com/tgcf/p/14777413.html