Boost.asio和UNIX信号处理

15

前言

我有一个使用 Boost.Asio 运行的多线程应用程序。整个应用程序只有一个 boost::asio::io_service,并且所有的操作都由一组线程在其中完成。有时需要使用 fork 和 exec 来生成子进程。当子进程终止后,我需要调用 waitpid 来检查退出代码并收集 zombie 进程。我最近使用了新增的 boost::asio::signal_set,但是在一些仍在使用 Linux 2.4.* 内核的古老系统中遇到了问题。在旧版本的 Linux 内核下,线程实际上是进程的一种特殊情况,因此如果一个子进程是由一个线程生成的,另一个线程将无法使用 waitpid 系列系统调用等待它。Asio 的 signal_set 会将信号处理程序投递到 io_service,任何运行该服务的线程都可以运行该处理程序,这对我的情况是不合适的。所以我决定用旧的好方法 signal/sigaction 来处理信号 - 所有线程都有相同的处理程序,该处理程序调用 waitpid。那么就出现了另一个问题:

问题

当处理程序捕获信号并成功等待进程时,如何从处理程序中“发布”到我的 io_service 中?我认为,明显的 io_service::post() 方法是不可能的,因为如果信号在错误的时间到达,它可能会造成 io_service 内部互斥锁死锁。唯一想到的方法是使用一些管道或 socketpair 将通知写入其中,然后在另一端使用 async_wait 处理,就像在处理 poll() 事件循环中的信号时一样。

还有更好的解决方案吗?


1
这也是我能想到的唯一事情。我感受到你为支持古老的Linux 2.4所付出的痛苦。 - Ringding
3
我认为唯一的另一种方式是构建你的代码结构,这样只有一个线程负责执行fork操作,可能需要它自己的io_service来完成fork。 - Dave S
谢谢,似乎为生成/回收子进程拥有独立的线程更加清晰,该线程具有自己的io_service,等待信号并执行打包任务以生成子进程。也许从性能角度来看它不是最好的,但代码更加清晰,并且仍然只使用boost信号处理,没有由异步信号安全函数调用组成的老式处理程序。 - Paul Graphov
2个回答

2
我没有处理过boost::asio,但我解决了类似的问题。我相信我的解决方案适用于LinuxThreads和更新的NPTL线程。
我假设你想要“post”信号到你的*io_service*是为了中断一个系统调用,以便线程/程序能够干净地退出。如果不是,请更好地描述您的最终目标。
我尝试了许多不同的解决方案,包括一些需要检测使用的线程类型的解决方案。最后帮助我解决这个问题的是man signal(7)标题为Interruption of System Calls and Library Functions by Signal Handlers的部分。
关键是在信号处理线程中使用没有SA_RESTARTsigaction(),为您想要捕获的所有信号创建处理程序,在信号处理线程中使用pthread_sigmask(SIG_UNBLOCK, sig_set, 0)取消屏蔽这些信号,并在所有其他线程中屏蔽相同的信号集。处理程序不必做任何事情。只是有一个处理程序可以改变行为,不设置SA_RESTART允许可中断的系统调用(如write())中断。而如果在其他线程中使用sigwait()系统调用,则不会中断。
为了在所有其他线程中轻松屏蔽信号。我启动信号处理线程。然后在启动任何其他线程之前,在主线程中屏蔽要处理的所有信号。然后,当启动其他线程时,它们会复制主线程的信号掩码。
关键是,如果您这样做,那么您可能不需要将信号发布到您的*io_service*,因为您可以仅检查您的系统调用以获取中断返回代码。但我不知道这在boost::asio中如何工作。
因此,所有这些的最终结果是,我可以捕获我想要的信号,如SIGINT,SIGTERM,SIGHUO和SIGQUIT,以执行干净的关闭,但我的其他线程仍然可以中断其系统调用,并且无需在信号处理程序和系统的其余部分之间进行任何危险的通信,并且单个实现适用于LinuxThreads和NPTL。
也许这不是你正在寻找的答案,但我希望它有所帮助。
注意:如果您想弄清楚系统是否正在运行LinuxThreads,您可以通过生成一个线程并将其PID与主线程的PID进行比较来实现。如果它们不同,则为LinuxThreads。然后您可以选择最佳线程类型的解决方案。

0

如果您已经在轮询您的IO,则另一种可能的解决方案非常简单,只需使用布尔值来信号其他线程。 布尔值始终为零或不是,因此不存在部分更新和竞争条件的可能性。 然后,您只需设置此布尔标志,而无需任何互斥量即可供其他线程读取。 类似valgrind的工具不会喜欢它,但实际上它是有效的。

如果要更正确,可以使用gcc的原子操作,但这是特定于编译器的。


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