1. 等待线程完成
若不等待线程完成,我们就需要确保该线程访问的数据都是有效的,直到该线程完成为止。比如如下代码,线程函数持有局部变量的指针或引用,当函数退出时,线程尚未执行完成。
#include <thread> #include <iostream> // 线程持有局部变量的指针 struct func { int *i; func(int *i_) : i(i_){ } void operator()() { for (unsigned j = 0; j < 100000; ++j) { *i = j; // 访问非法地址 } } }; // 不等待线程执行完成就退出 void oops() { int some_local_state = 0; func my_func(&some_local_state); std::thread my_thread(my_func); my_thread.detach(); } int _tmain(int argc, _TCHAR* argv[]) { oops(); return 0; }
要避免这种情况,需要使用std::thread实例的join()来替换my_thread.detach()的调用,这样就可以保证在函数退出前,线程已经结束。对一个给定的线程,只能调用一次join(),一旦调用了join(),此std::thread对象不再是可连接的,如果调用其的joinable()将返回false。
2. 在异常环境下的等待
我们需要在线程对象被销毁前调用join或detach方法,如果要detach,通常在线程启动后就立即调用detach方法。如果打算等待该线程,就需要仔细的选择在哪个位置调用join。如果在线程开始之后,调用join之前发生了异常,则可能跳过对join的调用。
为了避免应用程序在引发异常的时候被终止,你需要异常时也调用join。
void do_something_in_current_thread() { throw("error"); } // 不等待线程执行完成就退出 void oops() { int some_local_state = 0; func my_func(&some_local_state); std::thread my_thread(my_func); try { do_something_in_current_thread(); } catch (const char *err_msg) { my_thread.join(); throw; } my_thread.join(); }
try/catch块可以确保无论函数时正常退出还是异常退出,都调用了线程的join方法,但是try/catch块看起来很啰嗦,也容易导致作用于混乱,更简单的办法是使用RAII-Resource Acquisition Is Initialization并提供一个类,在析构函数中调用join():
class thread_guard { std::thread& t; public: explicit thread_guard(std::thread& t_) : t(t_) { } // 析构函数中检查线程是否还未被join,若没有,则调用 ~thread_guard() { if (t.joinable()) { t.join(); } } // 将拷贝后赋值运算符标记为=delete以避免编译器自动生成,复制或赋值这样一个对象可能很危险,因为它可能比它要结合的线程的作用域存在得更久。 thread_guard(thread_guard const&) = delete; thread_guard& operator=(thread_guard const&) = delete; }
版权声明:本文为博主原创文章,未经博主允许不得转载。
时间: 2024-12-20 16:13:48