为什么我需要显式分离一个短期变量?
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 的句柄即将丢失。现在应该怎么办?有几种可能性,最明显的如下:
- 线程 B 分离并继续独立执行
- 线程 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
级别:您可以告诉编译器您知道它不知道的东西,但这必须是明确的,而不仅仅是函数的结果 没有打电话。
假设我有一个小操作想在单独的线程中执行。我不需要知道它何时完成,也不需要等待它完成,但我不希望该操作阻塞我的当前线程。当我写下面的代码时,我会崩溃:
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 的句柄即将丢失。现在应该怎么办?有几种可能性,最明显的如下:
- 线程 B 分离并继续独立执行
- 线程 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
级别:您可以告诉编译器您知道它不知道的东西,但这必须是明确的,而不仅仅是函数的结果 没有打电话。