pthread_kill() 与 pthread_cancel() 终止因 I/O 而阻塞的线程

pthread_kill() vs pthread_cancel() to terminate a thread blocked for I/O

本文关键字:pthread 线程 kill cancel 终止      更新时间:2023-10-16

在我们的服务器代码中,我们使用 poll(( 系统调用来监视客户端套接字。调用 poll(( 的超时值较大。因此,调用 poll(( 的线程被阻止用于 I/O。

根据流程,我们有一个场景,我们需要终止来自不同线程的 poll(( 中阻塞的线程。我遇到了 pthread_kill(( 和 pthread_cancel(( 函数,它们可以终止因 I/O 而阻塞的目标线程。

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

有没有其他方法可以终止因 I/O 而阻塞的线程? 如果没有,建议使用其中哪个函数。

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

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

根据线程库的确切实现,线程很可能在被杀死时甚至不会从poll返回 - 因此,您甚至可能无法实现您想要的。

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

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

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

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

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

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

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

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

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