OS信号处理循环:应采用阻塞还是非阻塞读模式?
问题
我的应用程序设有一个OS信号处理线程processOSSignals,以避免阻塞programLoop()。该线程持续读取SIGINT、SIGTERM、SIGQUIT信号对应的文件描述符,收到信号时将初始值为true的loopOver设为false。
代码如下:
int mSigDesc = -1; void init() { // creates file descriptor for reading SIGINT, SIGTERM, SIGQUIT // blocks signals with sigprocmask(SIG_BLOCK, &mask, nullptr) ... mSigDesc = signalfd(mSigDesc, &mask, SFD_NONBLOCK); // OR 3rd param = 0? } void processOSSignals() { while (loopOver) { struct signalfd_siginfo fdsi; auto readedBytes = read(mSigDesc, &fdsi, sizeof(fdsi)); ... } } int main() { init(); std::thread ossThread(processOSSignals); programLoop(); ossThread.join(); }
我的问题是:mSigDesc应设置为阻塞模式还是非阻塞(异步)模式?
非阻塞模式下,线程会反复读取并返回EAGAIN,始终处于忙碌状态,效率低下;阻塞模式下,线程会等待信号到来,若从未收到信号,ossThread将无法join。
应如何处理?是在非阻塞模式中加入sleep()定期尝试读取,还是在阻塞模式下用select()监听mSigDesc仅在有数据时读取?
解决方案
推荐采用阻塞模式配合额外的终止通知机制,而非非阻塞加sleep或者单纯select,具体处理方式如下:
给信号处理线程增加一个额外的"终止通知"文件描述符(用
pipe()创建一对管道)- 在初始化流程中创建管道,将读端纳入信号处理线程的监听集合
- 当
programLoop()结束需要退出时,往管道写端写入一个字节,唤醒阻塞中的信号处理线程
修改
processOSSignals()逻辑,用select()同时监听信号fd和管道读端:
#include <sys/select.h> #include <unistd.h> #include <algorithm> #include <atomic> std::atomic<bool> loopOver = true; int mSigDesc = -1; void processOSSignals(int pipeReadFd) { fd_set readFds; while (loopOver) { FD_ZERO(&readFds); FD_SET(mSigDesc, &readFds); FD_SET(pipeReadFd, &readFds); int maxFd = std::max(mSigDesc, pipeReadFd); // 阻塞等待任一文件描述符就绪 int ret = select(maxFd + 1, &readFds, nullptr, nullptr, nullptr); if (ret == -1) { // 处理select错误,若被信号打断则继续循环 if (errno == EINTR) continue; break; } // 处理信号事件 if (FD_ISSET(mSigDesc, &readFds)) { struct signalfd_siginfo fdsi; ssize_t readedBytes = read(mSigDesc, &fdsi, sizeof(fdsi)); if (readedBytes == sizeof(fdsi)) { // 捕获到目标信号,终止循环 loopOver = false; } } // 处理主动终止通知 if (FD_ISSET(pipeReadFd, &readFds)) { char dummy; read(pipeReadFd, &dummy, 1); loopOver = false; } } }
- 调整main函数逻辑,加入管道的创建与通知:
int main() { int pipeFds[2]; if (pipe(pipeFds) == -1) { // 处理管道创建错误 return 1; } init(); // 确保mSigDesc被正确初始化 std::thread ossThread(processOSSignals, pipeFds[0]); programLoop(); // 主动通知信号处理线程退出 char dummy = '1'; write(pipeFds[1], &dummy, 1); ossThread.join(); // 关闭管道文件描述符 close(pipeFds[0]); close(pipeFds[1]); return 0; }
方案优势与其他思路对比
- 非阻塞加sleep:会引入信号响应延迟,sleep时间过短仍会占用不必要的CPU资源,过长则无法及时处理信号,体验不佳
- 单纯阻塞模式无通知:主程序正常结束时无法唤醒信号线程,导致
join()永久阻塞,程序无法正常退出 - 管道通知+select监听:既保证无信号时线程休眠(不占用CPU),又能在主程序结束时主动唤醒线程,确保
join()正常执行,同时信号响应无延迟
内容的提问来源于stack exchange,提问作者Piotr G
相关产品推荐
相关产品推荐

