对于pthread,如何从主线程中终止子线程

20

我使用pthread_create创建了多个子线程。此时,主线程希望杀死所有子线程,否则会出现段错误。我应该使用哪个函数来完成这个任务?我在谷歌上搜索到了像pthread_kill这样的函数,但我不知道应该发送哪个信号给子线程来杀死它们。我的运行环境是RHEL 5.4,编程语言是C。


2
哇,杀掉所有线程因为会出现段错误?你开玩笑吧?我永远不会购买这样处理的代码。 - Jens Gustedt
1
说实话,我使用gdb来调试主线程退出时发现的核心转储。因此,一些子线程可能正在使用这样的变量,并且在我的屏幕上会打印出段错误。尽管逻辑正确,但由于程序已经结束,所以段错误不会对我的程序产生任何影响。 - terry
2
修正逻辑。如果其他线程仍在执行有用的工作,为什么要试图终止程序?如果其他线程即使在main超出范围时仍需要这些对象,为什么它们被分配在main的堆栈上? - David Schwartz
4个回答

29

通常情况下,您不想暴力终止一个子线程,而是要求它终止。这样,您可以确保子进程在安全位置退出,并清理其所有资源。

我通常使用父子进程之间的共享状态来允许父进程向每个子进程发送“退出请求”。这可以是每个子进程的布尔值,由互斥量保护。子进程定期检查此值(每个循环迭代,或在您的子线程中方便的检查点)。一旦看到“quit_request”为真,子线程将进行清理并调用pthread_exit

在父进程方面,“kill_child”例程类似于以下内容:

acquire shared mutex
set quit_request to true 
pthread_join the child 

根据子线程检查其退出请求的频率,pthread_join可能需要一些时间。确保您的设计可以处理可能出现的任何延迟。


通常情况下,获取互斥锁以修改在线程之间共享的任何内存是一个好的实践方法。但由于这是一个单一标志,检查非零(或假)状态,意味着您实际上不需要获取互斥锁。在这种情况下,从数据一致性的角度来看,真的很少出现问题。 - Bernie Habermeier
2
@Brent:这个问题展示了一个例子,即使只有一个标志位被写入一次,在编译器优化后也可能导致竞态条件。 - dshepherd
如果子线程在调用pthread_join之前终止,会发生什么?线程不需要在加入时保证处于活动状态吗? - Benjamin Crawford Ctrl-Alt-Tut

22

使用 pthread_cancel 可以“取消”线程。但是,通常情况下这不是最佳实践,只有在极端情况下,比如SEGFAULT时,才可能考虑采用这种方法。


5
pthread_cancel为什么不被认为是最佳实践?如果不是最佳实践,那么什么是最佳实践? - darnir
@darnir pthread_cancel 就像 Ctrl-C,它可以工作并且有其用处,但不应该成为退出程序的“常规”方式。优雅地退出程序,释放资源、保存文件等是更好的选择。对于线程来说,最好发送一个标志给子线程,让它自己退出。 - Paul Hutchinson

8
你应该向每个线程发送SIG_TERM信号,使用以下代码:
  int pthread_kill(pthread_t thread, int sig);

除了主线程之外,一个快速摆脱所有线程的方法是使用fork()并继续进行子进程。
不是特别干净的解决方案...

  if (fork()) exit(0); // deals also with -1...

3
你可以为整个程序使用全局变量。
int _fCloseThreads;
当你想让线程停止执行时,将其设置为1。在它们的“循环”中检查该变量,并在其被设置为1时优雅地退出。不需要用互斥锁来保护它。
你需要等待线程退出。你可以使用join。另一种方法是在线程进入其线程过程时增加计数器,然后在线程退出时减少计数器。计数器需要是某种全局变量。在计数器上使用gcc原子操作。主线程在设置fCloseThreads后,可以通过循环、睡眠和检查计数来等待计数器归零。
最后,你可能会看看pthread_cleanup_push和pop。它们是一个模型,允许线程在其代码的任何位置取消(使用longjump),然后在退出线程过程之前调用一个最终的清理函数。你基本上将cleanup_push放在线程过程的顶部,将cleanup_pop放在底部,创建一个展开函数,然后在某些取消点上,通过调用pthread_cancel()取消线程将longjump返回到线程过程并调用展开函数。

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