SIGINT信号处理器中调用sem_post异常,寻求优雅唤醒线程方案
解决方案:优雅实现信号唤醒被动等待的主线程
问题根源
sem_wait() 是可中断的系统调用,当SIGINT信号触发时,内核会打断正在阻塞的sem_wait(),使其返回EINTR错误。你的循环重试虽然能解决问题,但不符合「被动挂起等待」的预期。下面是两种更优雅的实现方式:
方案一:利用SA_RESTART标志自动重启系统调用
POSIX允许通过设置sigaction的SA_RESTART标志,让被信号打断的可中断系统调用自动重启。对于sem_wait()这类支持该标志的系统调用,无需手动循环重试,就能实现被动挂起等待。
修改后的代码:
#include <stdio.h> #include <stdlib.h> #include <signal.h> #include <semaphore.h> #include <string.h> #include <errno.h> sem_t mutex; static void handler(int num) { sem_post(&mutex); } int main() { struct sigaction sa; sa.sa_handler = handler; // 添加SA_RESTART标志,让被信号打断的系统调用自动重启 sa.sa_flags = SA_RESTART; if (sigemptyset(&sa.sa_mask) == -1) { perror("sigemptyset failed"); return EXIT_FAILURE; } if (sigaction(SIGINT, &sa, NULL) == -1) { perror("sigaction failed"); return EXIT_FAILURE; } sem_init(&mutex, 0, 0); int sval; printf("Get ready, press Ctrl+C...\n"); while (1) { // 无需循环重试,SA_RESTART会让sem_wait被打断后自动重启 sem_wait(&mutex); sem_getvalue(&mutex, &sval); printf("sem is %d\n", sval); } printf("Bye"); return 0; }
优点:代码改动极小,逻辑简洁,完全符合被动挂起的预期。
注意:并非所有系统调用都支持SA_RESTART,但sem_wait()在主流POSIX系统(Linux、BSD)中均支持该行为。
方案二:Self-Pipe Trick(通用信号唤醒方案)
这是UNIX环境下处理信号与线程等待的经典模式,不受SA_RESTART的限制,且信号处理函数仅需执行异步信号安全的write()操作,通用性更强。
核心思路:
- 创建一个匿名管道;
- 信号处理函数向管道写入一个字节(无需关心内容,仅作为唤醒标志);
- 主线程用
select()/poll()/epoll()等待管道可读,实现被动挂起; - 管道可读时,读取字节并执行后续逻辑。
实现代码:
#include <stdio.h> #include <stdlib.h> #include <signal.h> #include <unistd.h> #include <sys/select.h> #include <string.h> int pipe_fd[2]; static void handler(int num) { // 向管道写入一个字节,仅用于唤醒主线程 char c = 'x'; // 忽略write的返回值(即使管道满也不影响,因为我们只需要触发可读事件) write(pipe_fd[1], &c, 1); } int main() { struct sigaction sa; fd_set read_fds; int ret; // 创建匿名管道 if (pipe(pipe_fd) == -1) { perror("pipe failed"); return EXIT_FAILURE; } sa.sa_handler = handler; sa.sa_flags = 0; if (sigemptyset(&sa.sa_mask) == -1) { perror("sigemptyset failed"); return EXIT_FAILURE; } if (sigaction(SIGINT, &sa, NULL) == -1) { perror("sigaction failed"); return EXIT_FAILURE; } printf("Get ready, press Ctrl+C...\n"); while (1) { FD_ZERO(&read_fds); FD_SET(pipe_fd[0], &read_fds); // 被动等待管道可读 ret = select(pipe_fd[0] + 1, &read_fds, NULL, NULL, NULL); if (ret == -1) { perror("select failed"); continue; } if (FD_ISSET(pipe_fd[0], &read_fds)) { char buf[10]; // 读取管道中的字节(清空管道,避免下次触发) read(pipe_fd[0], buf, sizeof(buf)); printf("Received SIGINT, woke up!\n"); } } close(pipe_fd[0]); close(pipe_fd[1]); printf("Bye"); return 0; }
优点:
- 完全脱离对
SA_RESTART的依赖,兼容性更强; - 可扩展支持多个信号或多个等待事件(只需在select中添加更多文件描述符);
- 信号处理函数逻辑极简,仅调用异步信号安全的
write(),避免潜在的线程安全问题。
内容的提问来源于stack exchange,提问作者Stepan .Kuznetsov
相关产品推荐
相关产品推荐

