如何向 std::thread 发出信号以正常退出

How do I signal a std::thread to exit gracefully?

本文关键字:常退出 退出 std thread 信号      更新时间:2023-10-16

使用 C++17,对于具有执行某些任务的非阻塞循环的工作线程,我看到三种指示线程退出的方法:

  1. 线程在循环中检查的std::atomic_bool。如果设置为true,线程将退出。主线程在调用std::thread::join()之前将其设置为true
  2. 一个boolstd::condition_variable。这与上述类似,除了它允许您调用std::condition_variable::wait_for()以有效地"休眠"线程(以降低 CPU 使用率),同时等待潜在的退出信号(通过设置bool,在第三个参数中检查wait_for()(谓词)。主线程将锁定互斥锁,将 bool 更改为true,并在调用std::thread::join()之前调用std::condition_variable::notify_all()以指示线程退出。
  3. std::futurestd::promise.主线程保存std::promise<void>,而工作线程保存相应的std::future<void>。工作线程使用类似于上述步骤的std::future::wait_for()。主线程在调用std::thread::join()之前调用std::promise::set_value()

我对每个想法:

  1. 这很简单,但缺乏在不显式调用std::this_thread::sleep_for()的情况下"减慢"工作线程循环的能力。似乎是执行线程信号的"老式"方式。
  2. 这个很全面,但非常复杂,因为你需要一个条件变量和一个布尔变量。
  3. 这似乎是最好的选择,因为它具有#1的简单性而没有#2的冗长。但我还没有std::futurestd::promise的个人经验,所以我不确定这是否是理想的解决方案。在我看来,promise&future旨在跨线程传递价值,而不是真正用作信号。所以我不确定是否存在效率问题。

我看到多种方式可以发出线程退出的信号。可悲的是,随着我一直在寻找,我的谷歌搜索只是引入了更多,并没有真正就C++17的"现代"和/或"最佳"方式达成共识。

我很想看到一些关于这种困惑的曙光。有没有一个决定性的、确定的方法可以做到这一点?普遍共识是什么?如果没有"一刀切",每种解决方案的优缺点是什么?

如果您有一个繁忙的工作线程,需要单向通知,如果它应该停止工作,最好的方法是只使用atomic<bool>。这取决于工作线程是否想要减慢速度或不想减慢速度。"限制"工作线程的要求与线程取消完全正交,在我看来,不应与取消本身一起考虑。据我所知,这种方法有两个缺点:您无法传回结果(如果有),也无法传回异常(如果有)。但是,如果您不需要其中任何一个,请使用atomic<bool>,不要打扰其他任何事情。它和任何一样现代;没有什么过时的

condition_variable是消费者/生产者模式的一部分。所以有些东西可以产生工作,有些东西会消耗生产的东西。为了避免在没有什么可消费的情况下忙于等待消费者,condition_variable是一个很好的选择。它只是此类任务的完美原语。但这对线程取消过程没有意义。无论如何,您都必须使用另一个变量,因为您不能单独依赖condition_variable。它可能会虚假地唤醒线程。您可能会在它进入等待过程之前"设置"它,完全丢失"设置",依此类推。它不能单独使用,所以我们回到原点,但现在有一个atomic<bool>变量来陪伴我们的condition_variable

当您需要知道在另一个线程上完成的操作的结果时,future/promise对是很好的。因此,它不是用atomic<bool>取代方法,而是补充它。因此,为了消除第一段中描述的缺点,您在等式中添加了future/promise。您向调用方提供从线程中存在的promise中提取的future。线程完成后,将设置该promise

  • 因为抛出异常。
  • 因为线程已经完成了它的工作并自行完成。
  • 因为我们要求它通过设置atomic<bool>变量来停止。

因此,如您所见,future/promise对只是有助于为被叫方提供一些反馈,这与取消本身无关。

附言您始终可以使用电动大锤来敲碎螺母,但这并不能使这种方法更加现代。

我不能说这是决定性的或确定性的,但由于这在某种程度上是一个意见问题,我会给出一个答案,它是基于大量的试验和错误来解决你问的那种问题(我认为)。

我的首选模式是使用原子布尔值向线程发出停止信号,并使用条件变量控制"循环"计时。

我们经常遇到在工作线程上运行重复任务的要求,因此我们创建了一个称为"threaded_worker"的类。 此类处理中止线程和计时对辅助角色函数的调用的复杂性。

中止是通过设置原子布尔值"abort"信号的方法处理的,该方法告诉线程停止调用工作函数并终止。

循环计时可以通过设置条件变量等待时间的方法进行控制。 可以通过调用条件变量上的通知的方法释放线程以继续。

我们将该类用作各种对象的基类,这些对象具有需要在单独的线程上执行的某些函数。 该类旨在运行一次或循环运行"work"函数。

我们使用布尔值进行中止,因为它简单且适合完成工作。 我们使用条件变量进行循环定时,因为它的好处是被通知"短路"时序。 当线程对象是使用者时,这非常有用。 当生产者对线程对象有工作时,它可以对工作进行排队并通知工作可用。 线程对象立即继续,而不是等待条件变量的指定等待时间。

两者(中止信号和条件变量)的原因是,我认为终止线程是一个函数,而将循环计时为另一个函数。

我们过去常常通过将线程置于睡眠状态一段时间来计时循环。 这使得在Windows计算机上几乎不可能获得可预测的循环计时。 有些计算机将在大约 1 毫秒后从睡眠(1)返回,但其他计算机将在 15 毫秒后返回。 我们的性能高度依赖于特定的硬件。 使用条件变量,我们大大改进了关键任务的时间安排。 在工作可用时通知等待线程的额外好处是值得条件变量的复杂性。