C++ 多线程的错误和如何避免(1)
在终止程序之前没有使用 join() 等待后台线程
前提分析:线程分为 joinable 状态和 detached 状态
添加 .join() 这句代码的时候,就表示主线程需要等待子线程运行结束回收掉子线程的资源后,再往下运行,否则就会产生一种情况:当子线程还没有运行完主线程先运行完了,那么就会结束这个进程,从而中断了子线程的运行。因此 join()函数的作用就是使主线程在此阻塞,等待子线程运行结束并回收其资源,再往下运行。
添加 .detach() 的作用是将主线程与子线程分离,主线程将不再等待子线程的运行,也就是说两个线程同时运行,当主线程结束的时候,进程结束。
在 detach 的时候,这个子线程将脱离主线程的控制,子线程独立分离出去并在后台运行。当主线程结束的时候,进程也就结束,所以子线程的输出不再显示出来,但是不会中断,会在后台继续运行,当子线程运行完以后,资源会被运行时库进行回收。
问题:如果我们在主程序终止之前忘记 join 或 detach,则会导致程序崩溃。
比如:
#include <iostream> #include <thread> using namespace std; void LaunchRocket() { cout << "Launching Rocket" << endl; } int main() { thread t1(LaunchRocket); // t1.join(); // somehow we forgot to join this to main thread - will cause a // crash. return 0; }
运行后,会报错。
解析:
这是因为在 main 函数结束时,线程 t1 超出范围后会自动析构,在析构过程中,会有一个检查来观察线程 t1 是否是 joinable 状态,如果是 joinable 状态,则调用 std::terminate
~thread() _NOEXCEPT { // clean up if (joinable()) _XSTD terminate(); }
而我们创建的 t1 没有添加 .join(),还处于 joinable 状态,此时析构时会调用 terminate(),而 terminate() 会默认调用 abort(),从而报错
补充:
1. joinable() 代表该线程是可执行线程,用于检测线程是否有效。
它会返回一个布尔值来表示当前的线程是否是可执行线程(能被 join 或者 detach),因为相同的线程不能 join 两次,也不能 join 完再 detach,同理也不能 detach
通常以下几种情况会导致线程成为 not-joinable
1)由 thread 的缺省构造函数而造成的(thread() 没有参数)。
2)该 thread 被 move 过(包括 move 构造和 move 赋值)。
3)该线程被 join 或者 detach 过。
比如:
... if (t1.joinable()) { cout << "joinable() == true" << endl; // 没有添加 .join(),joinable() == true } else { cout << "joinable() == fasle" << endl; // 添加 .join(),joinable() == true } ...
2. 摘自 https://en.cppreference.com/w/cpp/thread/thread/~thread
四种可以安全析构的情况是:
- 默认构造函数创建的 std::thread,在这种情况下,没有实际的线程被创建。
- 被移动过的线程,在这种情况下,移动的对象关联了线程而被移动的对象无关联线程。
- 调用了 join(),在这种情况下,join() 函数会堵塞直到被关联的线程执行结束。
- 调用了detach(),在这种情况下,被关联的线程会被解除关联。
参考: