C++11中如何安全终止线程?指针管理线程的疑问
嘿,这个问题问到点子上了!我来给你拆解清楚这两段代码的差异,以及正确终止线程的姿势。
为什么第一段代码报错,第二段却“看起来正常”?
第一段:栈上std::thread的情况
当你在栈上创建std::thread对象(比如t1、t2)时,main()函数返回前会自动调用这些对象的析构函数。而C++标准有个硬性规定:如果一个std::thread对象处于joinable状态(既没调用join()也没调用detach()),它的析构函数会直接触发std::terminate(),强制终止整个程序。这就是你看到报错terminate called without an active exception的根本原因。
对应的代码块:
class Greating { public: Greating(const int& _i):i_(_i){} ~Greating(){} int i_; void say() { std::cout << "Hello World" << i_ << std::endl; } }; int main(){ Greating greating1(1); Greating greating2(2); std::thread t1(&Greating::say, greating1); std::thread t2(&Greating::say, greating2); return 0; }
第二段:堆上std::thread的情况
你用new创建的std::thread* tt1、tt2是堆内存上的对象,main()返回时并不会自动调用它们的析构函数(堆内存必须手动用delete释放)。所以这些std::thread对象的析构逻辑没被执行,自然不会触发std::terminate()。
但这绝对不是“无需调用join()”,反而这是一种错误且危险的写法:
- 首先存在内存泄漏:堆上的
std::thread对象永远不会被delete,对应的内存会一直被占用。 - 其次是未定义行为:当
main()返回、进程退出时,操作系统会强制杀死所有还在运行的线程。这次你刚好看到了输出,只是运气好——换个环境或更复杂的线程逻辑,很可能出现崩溃、数据损坏等问题。 - 另外你看到的混乱输出
Hello WorldHello World12 Hello World12,是因为两个线程同时向std::cout输出,而cout本身不是线程安全的,导致输出内容被打乱了。
对应的代码块:
class Greating { public: Greating(const int& _i):i_(_i){} ~Greating(){} int i_; void say() { std::cout << "Hello World" << i_ << std::endl; } }; int main(){ Greating greating1(1); Greating greating2(2); std::thread* tt1 = new std::thread(&Greating::say, greating1); std::thread* tt2 = new std::thread(&Greating::say, greating2); return 0; }
如何正确终止线程?
正确的线程终止方式核心原则是:要么等待线程完成所有工作,要么明确分离线程让操作系统管理,主要有两种选择:
1. 使用join()(推荐,安全可控)
调用thread.join()会让当前线程(比如main())阻塞,直到目标线程完成它的可调用函数(比如say()),目标线程执行完毕后join()才会返回。这样能确保线程的所有逻辑都执行完成,资源被正确释放,也能避免线程访问的对象提前被销毁(比如你的greating1、greating2在main()里是栈对象,join()后它们才会被析构,不会出现悬空引用)。
修改后的安全示例:
int main(){ Greating greating1(1); Greating greating2(2); std::thread t1(&Greating::say, greating1); std::thread t2(&Greating::say, greating2); // 等待两个线程完成所有工作 t1.join(); t2.join(); return 0; }
2. 使用detach()(适合后台线程)
如果你不需要等待线程完成,比如一些后台任务,可以调用thread.detach(),把线程和std::thread对象分离。分离后的线程会变成“守护线程”,由操作系统负责管理,当它的任务完成后自动回收资源。
但要注意两个关键点:
- 分离后就不能再通过
std::thread对象操作这个线程了,也不能再调用join()。 - 必须确保线程访问的所有资源(比如
greating1)在线程运行期间一直有效,否则会出现悬空引用/指针,导致未定义行为。
示例:
int main(){ Greating greating1(1); Greating greating2(2); std::thread t1(&Greating::say, greating1); std::thread t2(&Greating::say, greating2); // 分离线程,让它在后台运行 t1.detach(); t2.detach(); // 这里main可以提前退出,但要确保greating1/2的生命周期覆盖线程运行时间 return 0; }
绝对不要做的事
- 不要像第二段那样用堆上的
std::thread对象却不delete,这会导致内存泄漏和未定义行为。 - 不要让
std::thread对象在joinable状态下被析构(不管是栈上还是堆上),否则会直接触发程序终止。
内容的提问来源于stack exchange,提问作者hanyang

