POSIX线程和信号

85

我一直在尝试理解 POSIX 线程和 POSIX 信号交互的复杂性。特别是,我感兴趣的问题有:

  • 控制信号传递到哪个线程是最好的方式(假设它在第一时间不是致命的)?
  • 告诉另一个可能很忙的线程信号已经到达,什么是最好的方式?(我已经知道从信号处理程序中使用 pthread 条件变量是一个坏主意。)
  • 如何安全地处理将信号发生的信息传递给其他线程?这需要在信号处理程序中进行吗?(通常情况下,我不想结束其他线程;我需要更加微妙的方法。)

为了参考,我正在研究如何转换 TclX 包以支持线程,或者将其分解并至少使某些有用部分支持线程。信号是其中特别感兴趣的一部分。

4个回答

52
  • 如何控制信号传递到哪个线程是最好的方式?

正如 @zoli2k 所指出的那样,明确指定单个线程来处理您想要处理的所有信号(或一组线程,每个线程都有特定的信号职责)是一种很好的技术。

  • 告诉另一个(可能真的很忙的)线程信号已经到达的最佳方法是什么?[...]
  • 如何安全地处理传递信号已发生的信息给其他线程?是否需要在信号处理程序中执行此操作?

我不会说“最好的”,但这是我的建议:

在主函数中阻塞所有需要的信号,以便所有线程都继承该信号掩码。然后,将专门接收信号的线程设计为以信号驱动的事件循环,将新到达的信号作为其他线程间通信进行分发。

最简单的方法是让线程使用sigwaitinfosigtimedwait循环接受信号。然后,该线程以某种方式转换信号,例如广播 pthread_cond_t,唤醒具有更多 I/O 的其他线程,在应用程序特定的线程安全队列中排队一个命令,等等。

另一种方法是,特殊线程可以允许信号递送到信号处理程序,只有在准备好处理信号时才解除屏蔽以进行递送。(不过,通过处理程序递送信号往往比通过 sigwait 家族接受信号更容易出错。) 在这种情况下,接收方的信号处理程序执行一些简单且异步信号安全的操作: 设置 sig_atomic_t 标志、调用 sigaddset(&signals_i_have_seen_recently, latest_sig)、向非阻塞的self-pipe写入一个字节等。然后,在其被屏蔽的主循环中,该线程向其他线程通报已收到信号,就像上面所述。

(更新@caf 正确指出 sigwait 方法更加优越。)


1
这是一个更有用的答案,特别是因为它也可以用于处理非致命信号处理。谢谢! - Donal Fellows
1
最简单的方法是信号处理线程根本不安装信号处理程序 - 相反,它在 sigwaitinfo()(或sigtimedwait())上循环,然后按照最后一段所述将它们分派到应用程序的其余部分。 - caf
@caf,确实如此。已更新。 - pilcrow

17

根据POSIX标准,所有线程应该在系统中显示相同的PID,并且可以使用pthread_sigmask()为每个线程定义信号阻塞掩码。

由于对于每个PID只允许定义一个信号处理程序,我更喜欢在一个线程中处理所有信号,并在运行的线程需要取消时发送pthread_cancel()。这是比使用pthread_kill()更可取的方法,因为它允许为线程定义清理函数。

在一些旧版本系统上,由于缺乏适当的内核支持,运行的线程可能与父线程的PID不同。有关在Linux 2.4上使用linuxThreads进行信号处理的常见问题解答


你所说的“implemented”是什么意思?此外,总是在响应信号时清除其他线程并不正确(SIGHUP和SIGWINCH需要更多的细节),而使用条件变量让其他线程知道则是不安全的。回答不佳。 - Donal Fellows
1
取消了我的负评,但这仍然不是一个充分的答案,因为我不能只是响应信号杀掉线程。在某些情况下,我会对本地事件进行排队以作出响应,在其他情况下,我必须非常仔细地拆除线程(顺便说一句,我已经有了大部分执行这些部分的机制;缺少的是与操作系统信号的连接)。 - Donal Fellows
1
@zoli2k:我最近尝试在新克隆的 uClibc git 主分支上运行 make menuconfig。确实存在一种选择旧的 LinuxThreads 和较新的 NPTL 作为 POSIX 线程实现方式,但截至2012年的帮助仍然建议不要选择 NPTL。因此,在现代嵌入式 Linux 系统中,即使系统运行的是足够新的 Linux 内核,仍然常见使用已过时的 LinuxThreads 实现。 - FooF

4

目前的情况:

  • 信号分为不同的主要类别,其中一些通常应该直接终止进程(SIGILL),而有些则从不需要任何操作(SIGIO;最好直接进行异步IO)。这两个类别无需采取任何行动。
  • 某些信号不需要立即处理;如SIGWINCH可以排队等待方便时再处理(就像来自X11的事件一样)。
  • 棘手的是那些你想通过中断正在进行的操作来响应它们,但又不想彻底清除线程的信号。特别是,在交互模式下,SIGINT应该使事物保持响应状态。

我仍然需要整理signal vs sigaction、pselect、sigwait、sigaltstack以及整个POSIX(和非POSIX)API的许多其他部分。


4

在我看来,Unix V信号和posix线程不太相容。Unix V诞生于1970年,而POSIX则是1980年的产物。

存在着取消点,如果你在一个应用程序中同时允许信号和pthread,你最终会发现需要在每个调用周围编写循环,这些循环可能会出现意外的EINTR错误。

因此,在我必须在Linux或QNX上进行多线程编程的(少数)情况下,我会为所有线程(但一个除外)屏蔽所有信号。

当Unix V信号到达时,进程切换堆栈(这就是Unix V中进程内并发所能实现的最大程度)。

正如其他帖子中的提示,现在可能可以告诉系统哪个posix线程将成为该堆栈切换的受害者。

一旦你成功地让你的信号处理线程工作起来,问题仍然存在,即如何将信号信息转换为其他线程可以使用的有意义的信息。需要一个线程间通信的基础设施。有用的一种模式是Actor模式,其中每个线程都是某个进程内消息传递机制的目标。

因此,与其取消其他线程或杀死它们(或其他奇怪的事情),你应该尝试将信号从信号上下文传递到你的信号处理线程,然后使用你的Actor模式通信机制向那些需要与信号相关信息的Actor发送语义上有用的消息。


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