【问题标题】:pthread_kill() vs pthread_cancel() to terminate a thread blocked for I/Opthread_kill() 与 pthread_cancel() 终止因 I/O 阻塞的线程
【发布时间】:2019-02-22 19:01:23
【问题描述】:

在我们的服务器代码中,我们使用 poll() 系统调用来监控客户端套接字。 poll() 以较大的超时值调用。所以调用 poll() 的线程被 I/O 阻塞。

根据流程,我们有一个场景,我们需要从另一个线程终止在 poll() 中阻塞的线程。我遇到过 pthread_kill() 和 pthread_cancel() 函数,它们可以终止为 I/O 阻塞的目标线程。

通过阅读手册页,这两个功能似乎都可以正常工作。互联网上很少有链接表明这两个功能使用起来很危险。

有没有其他方法可以终止因 I/O 阻塞的线程? 如果不是,建议使用这些功能中的哪一个。

【问题讨论】:

  • 杀死一个线程是有风险的。见this question
  • 更频繁地从poll 唤醒并测试由另一个线程设置的终止标志。如果标志说终止,则终止。否则循环并再次poll,直到你达到更广泛的退出条件。线程杀死应该是最后的手段。因为它会给你留下一个不稳定的程序
  • 既然你有这个标记的linux,一种方法是在你轮询的那些中包含一个eventfd()描述符,并在轮询线程应该退出时发出信号(并让该线程在fd 变得可读)。
  • @R.. 杀死威胁通常被理解为导致其执行立即且不可撤销地停止。一个特定的线程 API 是否允许它是另一个问题。
  • @R.. 你说得对。尽管如此,杀死一个线程是有风险的,而且仍然有 像杀死一个线程这样的事情。此信息仍然与 OP 相关,因为这是他们正在尝试做的事情,无论使用他们选择的 API 做这件事是不可能的。

标签: c++ c linux sockets pthreads


【解决方案1】:

根据你的线程库的具体实现,线程很可能在被杀死时甚至不会从poll 返回——所以,你甚至可能无法实现你想要的。

您需要非常小心不要造成内存泄漏,并且仍然很可能通过杀死拥有它的线程来创建文件描述符泄漏(注意,与进程相反,线程资源不是“清理"由系统)。

通常更安全的是使用较短的超时时间并在两者之间轮询终止标志,或者使用信号中断系统调用,然后在自己的控制下终止线程,释放所有分配的资源。

【讨论】:

  • @R.我认为这很明显是什么意思。如果你不喜欢我叫它杀戮,就叫它强制终止
  • @R.就像进程一样 - 该命令仍然称为kill。即使在那里,而且一直都在。
  • 这就是我要说的。而且,它仍然被称为kill。即使它不能杀死进程。即使它可以用来唤醒进程,这有点相反。我认为您在术语上吹毛求疵。
  • 终止线程的 posix 线程函数通过向它发送信号来完成此操作(就像kill 在向进程发送信号时所做的那样)。大多数 pthread 库使用非终止信号做同样的事情,因此需要线程的合作才能结束线程而不是整个进程。所以呢?对我来说仍然像kill。但是争论你是否可以“杀死”一个线程并不能真正回答这个问题。
  • @R..pthread_cancel 向目标线程发送一个 [特殊] 信号,目标线程拦截它,进行一些清理,然后终止。可以通过pthread_sigmask 屏蔽信号,它是sigprocmask 的包装[以防止意外屏蔽pthread_cancel 使用的信号],并且它在每个线程 的基础上工作。 kill(2) 可以被赋予一个线程 ID(来自 gettid)而不是进程 ID(来自 getpid
【解决方案2】:

一个简单而干净的选择是创建一个“信号”管道。也就是说,调用pipe,获取“读取”端的文件描述符并将其添加到poll 文件描述符列表(使用POLLIN)。然后,每当您想解除阻塞在poll 中等待的线程时,只需将一个字节写入管道的写入端即可。接收到数据的管道将在阻塞线程中返回为可读。您甚至可以通过改变写入字节的值来指定不同的“命令”。

(当然,您需要先从管道中读取字节,然后才能重新使用它。)

【讨论】:

  • +1 和附注:由于 OP [已经] 在做套接字 [因此,已经熟悉它们],我们可以用 AF_UNIX 套接字替换管道。它可以提供更好的 R/T 响应。不知道。
  • +1,当它工作时,这是一个非常好的方法,但是有些操作会阻塞,没有关联的文件描述符可以轮询。
【解决方案3】:

没有杀死线程这样的事情。

名称不佳的pthread_kill 函数是名称不佳的kill 函数的线程模拟,它向进程发送信号。 kill 这个名字在历史上是有意义的,因为许多信号的默认操作是终止进程。但是这种杀死进程的默认操作并不取决于信号是发送到进程还是特定线程 - 无论哪种方式,进程都会终止。

pthread_kill 唯一有用的时候是当您想在另一个线程上调用信号处理程序时。除非您确定信号处理程序不能中断任何非异步信号安全的函数,否则信号处理程序仅限于调用异步信号安全的函数,因此甚至无法结束线程的生命周期( pthread_exit 不是异步信号安全的)。

如果您对最终因调用而终止的线程感到满意,pthread_cancel 是结束卡在阻塞操作中的线程的正确方法。但是,为了安全使用它,您需要大量使用pthread_cleanup_pushpthread_cleanup_pop

如果您不希望线程终止,信号是您唯一的选择。你有两个选择:

  1. 使用sigaction 而不使用SA_RESTART 安装信号处理程序(可以是无操作),以便它导致EINTR。由于这种方法存在固有的竞争条件(如果您在进入阻塞系统调用之前发送信号,而不是一旦它被阻塞,信号将不会做任何事情)您需要重复发送信号,具有指数退避,以免使目标执行时间不足,直到目标通过其他一些同步机制(POSIX 信号量很好)确认它收到了消息。

  2. 安装将longjmp 的信号处理程序。为了安全地执行此操作,您需要控制可能发生的上下文;最简单的方法是在信号掩码中正常阻止它,仅当jmp_buf 在阻塞调用周围有效时才取消掩码。您调用的阻塞函数必须是异步信号安全的,并且它必须不是分配或释放资源的函数(如openclose),因为您会输了解它是否在您处理信号时完成。当然,jmp_buf 或指向它的指针必须是线程本地对象 (_Thread_local/__thread) 才能正常工作。

【讨论】:

  • 有人对我指出没有杀死线程这样的事情感到不安(在 POSIX 线程的上下文中,问题已被标记)。
猜你喜欢
  • 2019-04-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-11
  • 1970-01-01
  • 2016-09-16
相关资源
最近更新 更多