【问题标题】:POSIX threads and signalsPOSIX 线程和信号
【发布时间】:2011-02-04 05:30:48
【问题描述】:

我一直试图了解 POSIX 线程和 POSIX 信号如何交互的复杂性。我特别感兴趣的是:

  • 控制信号传递到哪个线程的最佳方法是什么(假设它首先不是致命的)?
  • 告诉另一个线程(实际上可能很忙)信号已到达的最佳方法是什么? (我已经知道从信号处理程序中使用 pthread 条件变量是个坏主意。)
  • 如何安全地处理将发生信号的信息传递给其他线程?这需要在信号处理程序中发生吗? (我一般不想杀死其他线程;我需要一种更微妙的方法。)

作为我想要这个的参考,我正在研究如何将TclX 包转换为支持线程,或者将其拆分并至少使一些有用的部分支持线程。信号是特别令人感兴趣的部分之一。

【问题讨论】:

    标签: c pthreads signals


    【解决方案1】:
    • 控制哪个线程的最佳方法是什么 信号传送到?

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

    • 告诉另一个线程(实际上可能很忙)的最佳方式是什么 信号已经到达?[...]
    • 如何安全地处理传递信号已发生的信息 到其他线程?这需要在信号处理程序中发生吗?

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

    阻止main 中所有需要的信号,以便所有线程都继承该信号掩码。然后,将特殊的信号接收线程设计为信号驱动的事件循环,将新到达的信号作为其他一些线程内通信进行调度。

    最简单的方法是让线程使用sigwaitinfo or sigtimedwait 在循环中接受信号。然后线程以某种方式转换信号,可能广播pthread_cond_t,用更多 I/O 唤醒其他线程,将命令排入特定于应用程序的线程安全队列,等等。

    或者,特殊线程可以允许将信号传递给信号处理程序,仅在准备好处理信号时才取消屏蔽传递。 (但是,通过处理程序传递信号往往比通过sigwait 系列接受信号更容易出错。)在这种情况下,接收器的信号处理程序执行一些简单且异步信号安全的操作:设置sig_atomic_t 标志,调用sigaddset(&signals_i_have_seen_recently, latest_sig)write() 一个字节到非阻塞self-pipe 等。然后,回到其被屏蔽的主循环,线程将接收到的信号传达给上述其他线程。

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

    【讨论】:

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

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

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

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

    【讨论】:

    • 你所说的“实施”是什么意思?此外,总是对其他线程进行核对以响应信号是不正确的(SIGHUP 和 SIGWINCH 需要更微妙),但是使用条件变量让其他线程知道是不安全的。不好的答案。
    • 删除了我的反对票,但这仍然不是一个足够的答案,因为我不能仅仅杀死线程来响应信号。在某些情况下,我将在本地排队事件作为响应,在其他情况下,我必须非常小心地拆除线程(顺便说一句,我已经拥有大部分机器来完成这些部分;它是与操作系统的连接丢失的信号)。
    • @zoli2k:我最近刚刚尝试使用新克隆的 uClibc git master 分支运行 make menuconfig。在旧的 LinuxThreads 和较新的 NPTL 作为 POSIX 线程实现之间有一个选择,但截至 2012 年的帮助仍然建议不要选择 NPTL。因此,在现代嵌入式 Linux 系统中,使用过时的 LinuxThreads 实现仍然很常见,即使系统运行的是足够新的 Linux 内核。
    【解决方案3】:

    我目前所处的位置:

    • 信号有不同的主要类别,其中一些通常应该直接终止进程 (SIGILL),而另一些则不需要执行任何操作(SIGIO;更容易直接执行异步 IO)。这两个类不需要采取任何行动。
    • 有些信号不需要立即处理; SIGWINCH 之类的可以排队,直到方便为止(就像来自 X11 的事件一样)。
    • 棘手的问题是您希望通过打断您正在做的事情来回应他们,但又不会达到消除线程的程度。特别是,交互模式下的 SIGINT 应该让事情做出响应。

    我仍然需要对signalsigactionpselectsigwaitsigaltstack 以及一大堆其他点点滴滴的 POSIX(和非 POSIX)API 进行分类。

    【讨论】:

      【解决方案4】:

      恕我直言,Unix V 信号和 posix 线程不能很好地混合。 Unix V 是 1970 年。POSIX 是 1980 年 ;)

      存在取消点,如果您在一个应用程序中允许信号和 pthreads,您最终将围绕每个调用编写循环,这可能会令人惊讶地返回 EINTR。

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

      当 Unix V 信号到达时,进程切换堆栈(这在 Unix V 中与进程中的并发性一样多)。

      正如这里的其他帖子所暗示的那样,现在可以告诉系统哪个 posix 线程将成为堆栈切换的牺牲品。

      一旦你设法让你的信号处理线程工作,问题仍然存在,如何将信号信息转换为文明的东西,其他线程可以使用。需要用于线程间通信的基础结构。一种有用的模式是参与者模式,其中每个线程都是某些进程内消息传递机制的目标。

      因此,与其取消或杀死其他线程(或其他奇怪的东西),不如尝试将 Signal 从 Signal 上下文编组到 Signal 处理程序线程,然后使用 Actor 模式通信机制将语义上有用的消息发送到那些需要信号相关信息的参与者。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多