为什么在销毁线程之前必须先调用join()或detach()?

时间:2019-07-16 23:55:38

标签: c++ multithreading

我不明白为什么std::thread被破坏时,它必须处于join()或detach()状态。

Join等待线程完成,而detach则没有。 似乎有些中间状态我不了解。 因为我的理解是join和detach是互补的:如果不调用join(),则默认为detach()。

这样,假设您正在编写一个创建线程的程序,并且仅在此线程的生命后期才调用join(),直到调用join为止,该线程基本上都像在运行一样超然,不是吗?

逻辑上detach()应该是线程的默认行为,因为这是线程的定义,它们与其他线程无关地并行执行。

因此,当线程对象被破坏时,为什么要调用终止()?为什么标准不能简单地将线程视为已分离?

我不了解在破坏线程之前未调用join()或detached()时终止程序的原因。这样做的目的是什么?

4 个答案:

答案 0 :(得分:29)

从技术上讲,答案是“因为规范如此规定”,但这是一个晦涩的答案。我们无法读懂设计师的想法,但是以下一些问题可能是造成这些问题的原因:

对于POSIX pthread,子线程必须在退出后再加入,否则子线程将继续占用系统资源(例如内核中的进程表条目)。这是通过pthread_join()完成的。 如果进程对子线程持有一个HANDLE,则Windows有一个类似的问题。尽管Windows不需要完全连接,但是该进程仍必须调用CloseHandle()才能在线程上释放其引用计数。

由于std::thread是跨平台的抽象,因此受到需要连接的POSIX要求的约束。

从理论上讲,std::thread析构函数可以调用pthread_join()而不是引发异常,但是(主观上)这可能增加死锁的风险。正确编写的程序将知道何时在安全时间插入联接。

另请参阅:

答案 1 :(得分:13)

您会感到困惑,因为您正在将std::thread对象与它所引用的执行线程进行混淆。 std::thread对象是C ++对象(内存中的一堆字节),它充当对执行线程的引用。当您调用std::thread::detach时,会发生std::thread对象与执行线程“分离”的情况-它不再引用(任何)执行线程,并且执行线程继续独立运行。但是std::thread对象仍然存在,直到被销毁为止。

执行线程完成后,它将退出信息存储到引用它的std::thread对象中,如果有一个(如果是分离的,则没有一个,因此退出信息为只会被扔掉。)它对std::thread对象没有其他影响-特别是std::thread对象没有被销毁,并且一直存在,直到有人销毁它为止。

答案 2 :(得分:1)

您可能希望线程在完成处理后完全清除而不会留下任何痕迹。这意味着您可以启动一个线程,然后将其忽略。

但是,您可能还希望能够在线程运行时对其进行管理,并获取完成后提供的任何返回值。在这种情况下,如果线程在完成后自行清理,则尝试管理该线程可能会导致崩溃,因为您将访问的句柄可能无效。为了在线程结束时检查返回值,必须将返回值存储在某个位置,这意味着无法完全清理线程,因为必须保留返回值的存储位置。

在大多数框架中,默认情况下,您会获得第二个选项。您可以管理线程(通过中断线程,向线程发送信号,加入线程或进行其他操作),但是无法对其进行清理。如果您更喜欢第一个选项,则可以使用一个函数来获取该行为(分离),但这意味着您可能无法访问该线程,因为该线程可能存在或可能不存在。

答案 3 :(得分:0)

当活动线程的线程句柄超出范围时,您有两种选择:

  1. 加入
  2. 分离
  3. 杀死线程
  4. 杀死程序

这些选项中的每一个都是可怕的。无论您选择哪一个,都会感到惊讶,困惑,而不是大多数情况下想要的。


可以说,您提到的连接线程已经以std::async的形式存在,这给您一个std::future阻塞,直到创建的线程完成为止,因此进行隐式连接。但是关于为什么的许多问题

std::async(std::launch::async, f);
g();

不会同时运行fg来表明这是多么令人困惑。我知道的最好的方法是将其定义为编程错误并由程序员修复,因此assert是最合适的。不幸的是,该标准改为使用std::terminate


如果您真的想要一个分离线程,只需在std::thread周围写一个包装器,即可在其析构函数或所需的任何处理程序中执行if (thread.joinable()) thread.detach();