高效退出多线程应用程序(细节说明)

6

我研究了一些关于在线程间优雅地传递消息以使所有线程都能正常退出的正确方法。其中,我喜欢使用全局原子布尔变量的想法,该变量可以从任何线程中设置标志,而所有其他线程检查此标志以执行退出程序 - 当所有线程都加入时,主线程就可以退出应用程序。

仅进行计算的线程可能会有不同的处理方式,对吗?

这种方法是否高效且安全?有更好的方法吗?

谢谢!

2个回答

4

我不喜欢使用线程检查布尔(或其他)状态变量来知道何时执行某些操作,因为这是浪费的。线程将不得不旋转,不断检查变量是否有新指令。这会消耗CPU。

更好的选择是创建一个信号量或在Windows中创建一个事件,并让所有线程等待它。线程可以在空闲时休眠,而不会从其他正在执行真正工作的线程中窃取时间片,只是为了检查变量。


(我对并行理论不太熟悉)我的应用程序仅限于Windows - 线程如何等待事件?你能指给我任何资源吗? - oneminute
1
@Kra - 更可能的情况是线程将调用WaitForMultipleObjects,其中一个句柄允许发出退出信号,另一个(或多个)句柄允许发出工作准备好的信号。如果在退出句柄上收到信号,则退出。 - Steve Townsend
1
这不就跟检查全局布尔值基本上是一样的吗?你仍然需要根据 waitformultipleobjects 的返回值来检查退出句柄是否已被激活。 - oneminute
1
@oneminute 使用WaitForMultipleObjects()的优点是线程可以高效地什么都不做(只是睡眠),但仍能快速响应请求(无论是退出还是执行某些工作)。如果您检查全局布尔值,则必须权衡频繁检查(即烧掉更多CPU周期)或对全局布尔值的更改响应较慢(当它在您的线程检查后发生更改时)。 - Stephen C. Steel
@oneminute - 正如 @Stephen 所指出的那样,这种方法的优点在于线程只有在真正有事情要做时才会被唤醒 - 如果没有工作,也没有终止,则 WaitForMultipleObjects 不会完成。 - Steve Townsend
显示剩余4条评论

1
在Windows中,我使用QueueUserAPC调用一个抛出异常的函数,从而使线程干净地退出。
我在这里的答案中详细介绍了更多细节: 如何保证我的win32应用程序快速关闭? 总之,以下是发生的情况:
假设线程A想要终止线程B(然后是C、D等)。
  • 线程A调用QueueUserAPC(),传递给线程B的句柄和一个函数的地址,该函数将抛出MyThreadExit类的异常。
  • 线程B正常运行,直到它调用检查可警告等待的某些内容。也许是WaitForSingleObjectEx,也许是SleepEx,或者其他什么。
  • 此时,线程B运行先前传递的APC函数,导致在线程B中抛出异常。
  • 随着异常使线程B“展开”其堆栈,所有堆栈分配的对象都会自动正确地析构。
  • 线程B的最外层线程函数将捕获异常。
  • 现在,线程B退出,可能向线程A发出完成信号。

听起来很聪明,但每次阅读它时我就感觉太粗糙了,无法摆脱这种感觉... - Necrolis
我承认这不是可移植的,但大多数其他解决方案真的会限制您在线程中使用的第三方代码。我在网络编程中大量使用线程,并需要可以安全“停止”的第三方网络库(例如Indy)。 - Roddy
我仍然无法理解,即使有更详细的解释。当您想要在线程内部退出时,可以使用QueueUserAPC,这最终调用一个抛出异常的函数,并且这个异常会传递到每个线程?析构函数是如何被调用的,抛出异常的函数又是什么样子的?(编程中的这整个领域对我来说都是新的)谢谢! - oneminute
@oneminute:大致的想法是,QueueUserAPC(Thread1, &foo) 会在 Thread1 进入“可警告等待状态”时立即调用 foo。如果 foo 抛出异常,则会取消 Thread1 的堆栈。也就是说,你一次只能杀死一个线程,就像 TerminateThread 所做的那样。但与 TerminateThread 不同的是,RAII 互斥锁会被解锁等。 - MSalters
@John Dibling:谢谢,确实有很多。我也在生产代码中使用这种技术。 - Roddy
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接