背景
在调查这个问题后,我了解到:
是.此外,发送到整个进程的信号最多只能传递到它的一个线程.这是两个不同的概念,尽管密切相关.
是的,尽管更准确的说法是每个线程does都有自己的信号掩码.即使线程的信号掩码没有阻止任何信号,它仍然在那里.
这对我来说也变得很明显,因为我有这样的问题,除非
在Accept()/recv()上被阻塞的特定线程捕获
信号时,线程将继续被阻塞为阻塞
函数不会返回EINTR.
是.函数调用以EINTR
失败意味着该调用的执行被中断,以允许调用线程运行信号处理程序,并且该处理程序是在没有SA_RESTART
选项的情况下安装的,或者有问题的函数不在支持中断时重新启动的函数之列.除处理信号的线程外,信号处理程序正在处理的信号对其他线程没有直接影响.
最初的设计
- 为什么每次我测试原始设计(其中主线程是服务器)都能正常工作?我已经测试过了
上百次了.我可以想象,在某种程度上,其中一个
线程将接收到该信号,并且主线程将
继续封锁.为什么这种情况从来没有发生过?
对于如何从没有阻塞给定信号的线程中 Select 处理该信号的线程,没有任何规范.没有要求 Select 是不确定的,这样您测试的次数就会有很大的相关性.话虽如此,您的原始代码中可能至少存在一个竞争,如果SIGTERM
到达的时间完全错误,则应用程序不会干脆关闭,但这很容易成为百万分之一级别的事件.
除此之外,为了 Select 一个线程来处理传入的信号,系统会以固定的顺序判断符合条件的线程,这可能与它们的创建顺序有关.当当前在I/O操作上被阻塞的线程可用时,优先 Select 它们也是合理的.这些是系统实现细节的示例,可以解释您的旧设计运行良好的原因.
新的设计选项
- 你有什么建议来纠正这个问题?我希望继续让服务器在派生的线程中运行,而不是在
主要的一个.
这有几个不同的方面,你似乎没有清楚地划分其中的各个方面.应用程序必须
- 接收
SIGTERM
或其他终止信号.
- 确保指示所有线程终止,包括
- 确保所有线程notice的指令及时关闭.
若要在接收到信号时自定义程序行为,需要安装自定义信号处理程序.你已经做了这么多.
您目前正在使用一个原子变量作为标志来通知您的线程已请求关闭. 这是合理的.
您正在努力解决的主要问题是(3)如何确保所有线程及时注意到关闭请求.正如您所发现的,至多一个线程的阻塞syscall将被中断,因此无限期阻塞的系统调用是一个需要解决的问题.
以下是我读到的一些解决方案:
指定的信号捕获线程
A.派生一个单独的信号捕获器线程,并阻止所有
其他的线索.
指定一个特定的线程来接收任何入站SIGTERM
非常有用,因为这为您提供了一种在普通代码中执行大部分响应的方法,而不是受到信号处理程序行为的限制.这是一个比您可能意识到的更大的优势,但它是一个与您所要求的不同的问题的解决方案.
通过信号通知其他线程
我不确定这对我的处境有什么帮助
因为如果服务器线程在系统调用上被阻塞并被屏蔽
所有信号,将无法向它发出唤醒和启动的信号
清理.
除了SIGTERM
处理程序外,其他线程不需要阻塞all个信号.可能还有其他一些他们也应该阻止的,但他们可能会允许一些.SIGTERM
处理程序可以通过向每个人发送这些信号中的一个来提醒他们.一些合理的 Select 可能是SIGABRT
、SIGALRM
、SIGUSR1
或SIGUSR2
.
无论是否由指定的线程负责,通过信号通知其他线程的优点是它可以中断(一些)阻塞的系统调用,但这有一个问题. 假设一个应用程序这样做...
something_nonblocking();
block_indefinitely();
if (I_should_terminate()) {
clean_up();
return;
}
如果该线程在其执行something_nonblocking()
时接收到通知信号,则该信号将不会中断block_indefinitely()
.即使something_nonblocking()
中的最后一件事是判断是否I_should_terminate()
,信号也有可能在确定判断结果之后(如false
)但在输入block_indefinitely()
之前到达,从而使信号对于实现迅速、干净的关机无效.
您可以通过发送两次信号来减少此问题出现的可能性,但您不能完全消除它.
通过I/O通知其他线程
提醒其他线程终止的另一种 Select 是...
B.切换到使用非阻塞套接字,并使用
Select ()/Poll()/EPOLL().我可以看到这是可行的,尽管看起来
对于一次只能处理一个客户端的服务器来说是过度杀伤力.
我不明白你为什么认为这是过度杀戮. 关键是避免I/O阻塞,防止您立即执行准备好的工作(即关闭). 这正是这些功能的目的.
我想您可能只是想使用它们来设置阻塞I/O的超时时间,这是可行的,但是如果您使用这种通用方法,那么您应该考虑设置一个I/O通道--例如管道--通过它可以将终止通知直接传送到I/O线程(S).如果该线程在其监视的FD中包括这样的通道的读取端,则不仅将存在真正的多路复用,而且该线程将更好地响应关闭通知,因为它不必等待超时到期.
然而,如果这是最好的解决方案,我愿意这样做.但确实是这样
这意味着所有生成的线程实际上都被禁止了
阻止系统调用?
说大也大吧.如果您确信(或至少愿意假设)syscall不会阻塞很长时间,那么阻止syscall是可以的.例如,本地文件系统上常规文件的I/O通常通过阻塞操作进行,但只有在特殊情况下,这样的操作才会阻塞足够长的时间来干扰您的关机.
另一条线索还没有
写入应该执行一些串行I/O.这是否也需要
用这些多路传输函数写的吗?
也许吧.如果这些串行I/O操作阻塞的时间可能比您愿意等待线程关闭开始的时间更长,则实现终止行为目标需要对其进行控制.用其中一个多路传输函数对它们进行选通是实现这一点的一种方法.依靠一两个信号来打断他们可能是另一回事.我会建议 Select 一种方法,并始终如一地使用它,但这是一个设计 Select .
总体建议
- Do use a dedicated signal-handling thread. In particular, I would suggest that
- 注册一个什么都不做的信号处理程序,用于
SIGTERM
和任何其他你想由这个线程处理的信号
- 这些信号通过它们的信号掩码为所有其他线程阻塞
- 处理程序线程的所有信号but都将被阻塞
- 处理机通过
pause()
或sigsuspend()
同步等待信号
- 请务必使用
select
/poll
/或epoll
来控制I/O操作的执行,否则可能会阻塞比您愿意等待关闭开始的时间更长的I/O操作
- 一定要设置一个管道或类似的I/O通道,可以将其添加到多路复用器的监视集中,并让信号处理线程使用它来确保线程在需要帮助时及时注意到终止信号.
- 当信号处理线程检测到信号时,在设置全局原子标志以记录该信号后,它将写入此通道
- 其他线程只需要看到该通道是可读的. 他们不需要实际使用其中的任何数据,也不应该这样做,这样它就保持了可读性.