为什么我需要显式分离一个短期变量?

Why do I need to explicitly detach a short term variable?

假设我有一个小操作想在单独的线程中执行。我不需要知道它何时完成,也不需要等待它完成,但我不希望该操作阻塞我的当前线程。当我写下面的代码时,我会崩溃:

void myFunction() {
    // do other stuff
    std::thread([]()
    {
        // do thread stuff
    });
}

通过将线程分配给变量并将其分离来解决此崩溃问题:

void myFunction() {
    // do other stuff
    std::thread t([]()
    {
        // do thread stuff
    });
    t.detach();
}

为什么需要这一步?或者有没有更好的方法来创建一个小型的一次性线程?

Because the std::thread::~thread() specification says so:

A thread object does not have an associated thread (and is safe to destroy) after

  • it was default-constructed
  • it was moved from
  • join() has been called
  • detach() has been called

看起来 detach() 是其中唯一对您的情况有意义的,除非您想 return 线程对象(通过移动)给调用者。

Why is this step necessary?

考虑线程对象代表一个长运行执行“线程”(轻量级进程或内核可调度实体或类似实体)。

允许您在线程仍在执行时销毁对象,使您无法随后加入该线程(并找到该线程的结果)。这可能是一个逻辑错误,但它也可能使正确退出程序变得困难。

Or is there a better way to create a small single-use thread?

不明显,但通常更好的做法是在后台为 运行 任务使用线程池,而不是启动和停止大量短期线程。

您也许可以使用 std::async() 代替,但是 future 它 returns 可能 在某些情况下会在析构函数中阻塞,如果您尝试丢弃它。

参见the documentation of the destructor of std:thread

If *this has an associated thread (joinable() == true), std::terminate() is called.

您应该明确表示您不关心线程会发生什么,并且您可以放弃对它的任何控制。这就是 detach 的目的。

总的来说,这看起来像是一个设计问题,所以崩溃是有道理的:很难就这种情况下应该发生的事情提出一个普遍的、不足为奇的规则(例如,你的程序可能会正常结束它的执行——什么线程应该发生什么?)。

考虑一下:线程A创建了线程B,线程A离开了它的执行范围。线程 B 的句柄即将丢失。现在应该怎么办?有几种可能性,最明显的如下:

  1. 线程 B 分离并继续独立执行
  2. 线程 A 在退出它自己的范围之前等待(加入)线程 B

现在你可以争论哪个更好了:1 还是 2?我们(编译器)应该如何决定其中哪一个更好?

所以设计者所做的是不同的:崩溃 终止代码,以便开发人员明确选择其中一种解决方案。为了避免隐含的(可能是不需要的)行为。这是给你的信号:"hey, pay attention now, this piece of code is important and I (the compiler) don't want to decide for you".

基本上,您的用例需要调用 detach(),因为您的用例 非常奇怪 ,而不是 C++ 试图简化的内容。

虽然 Java 和 .Net 愉快地让你扔掉一个 Thread 对象,其关联线程仍然是 运行,但在 C++ 模型中 Thread 更接近于 成为 线程,在某种意义上,Thread 对象的存在与它所指的执行的生命周期或至少可连接性一致。请注意如何在不启动它的情况下创建 Thread (默认构造函数除外,它实际上只是在移动语义服务中存在),或者复制它或从线程创建一个ID。 C++ 希望 Thread 比线程长。

保持这种状态有多种好处。线程控制数据的最终清理不必由 OS 自动完成,因为一旦 Thread 消失,就没有任何东西可以尝试加入它。更容易确保具有线程存储的变量被及时销毁,因为主线程是最后退出的(除非有一些移动恶作剧)。缺少的 join —— 这是一种 极其 常见的错误类型 —— 在运行时被正确标记。

相比之下,允许让一些线程飘到远处,但这是一件不寻常的事情。除非它通过同步对象与您的其他线程进行交互,否则无法确保它完成了它想要做的事情。分离的线程处于 reinterpret_cast 级别:您可以告诉编译器您知道它不知道的东西,但这必须是明确的,而不仅仅是函数的结果 没有打电话。