如何通过SIGINT信号立即终止C语言中长期运行的无限循环线程
解决方案
核心原因
你遇到的阻塞问题本质是:getline()内部依赖read()这类慢速系统调用,默认情况下内核会为信号处理函数自动重启被中断的系统调用,所以SIGINT处理完成后getline()会继续阻塞,不会返回。
可行实现方案
方案1:调整信号配置,中断阻塞的系统调用
- 替换
signal()为sigaction(),关闭SA_RESTART标志,禁止系统调用自动重启,这样SIGINT触发后getline()会立刻返回-1,errno被设置为EINTR。 - 多线程环境下建议统一让主线程处理信号,子线程屏蔽SIGINT,避免信号随机递送到子线程导致逻辑混乱。
- 调整
getline()的错误判断逻辑,返回-1时检查是否为信号中断,如果是则判断全局退出标记。
修改后的核心代码示例:
#include <signal.h> #include <errno.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> volatile sig_atomic_t exitRequested = 0; // 信号处理函数不要调用printf,它不是异步信号安全的 void sigint_handler(int sig) { const char msg[] = "handler called\n"; write(STDOUT_FILENO, msg, sizeof(msg)-1); exitRequested = 1; } void* process_tickers(void* _) { // 子线程屏蔽SIGINT,不处理信号 sigset_t set; sigemptyset(&set); sigaddset(&set, SIGINT); pthread_sigmask(SIG_BLOCK, &set, NULL); while (1){ printf("ticker is running\n"); if (exitRequested == 1) { break; } // 这里假设dec_time已经正确定义 if (dec_time == -1) { size_t bufsize = 32; char *buffer = malloc(32); size_t characters; characters = getline(&buffer,&bufsize,stdin); if (characters > 0) { // do something } else if (characters == -1 && errno == EINTR) { // 被信号中断,检查退出标记 if (exitRequested == 1) { free(buffer); break; } } free(buffer); // 补充内存释放,避免泄漏 } } return NULL; } int main() { // 主线程先屏蔽SIGINT,保证创建的子线程都继承屏蔽配置 sigset_t set; sigemptyset(&set); sigaddset(&set, SIGINT); pthread_sigmask(SIG_BLOCK, &set, NULL); pthread_t ticker_thread; pthread_create(&ticker_thread, NULL, process_tickers, NULL); insertRear(threads, &ticker_thread); // 主线程解除SIGINT屏蔽,注册信号处理函数 struct sigaction sa = {0}; sa.sa_handler = sigint_handler; sa.sa_flags = 0; // 不设置SA_RESTART,禁止系统调用自动重启 sigemptyset(&sa.sa_mask); sigaction(SIGINT, &sa, NULL); pthread_sigmask(SIG_UNBLOCK, &set, NULL); // 注意:原来的代码pthread_join会阻塞到线程退出才会执行后面的循环 // 如果main也有业务逻辑要跑,建议把pthread_join放到业务循环结束后 void * retval = NULL; pthread_join(ticker_thread, &retval); while (1) { // do something; } }
方案2:用IO多路复用避免长期阻塞
如果不想修改信号配置,可以在调用getline()前用poll()监听stdin可读事件,设置短超时,每次超时就检查退出标记:
#include <poll.h> // process_tickers里的读逻辑替换为: struct pollfd pfd = {.fd = STDIN_FILENO, .events = POLLIN}; // 超时100ms,最多阻塞100ms就返回检查退出标记 int ret = poll(&pfd, 1, 100); if (ret > 0 && (pfd.revents & POLLIN)) { // 有数据可读,调用getline characters = getline(&buffer,&bufsize,stdin); if (characters > 0) { // do something } } else if (ret < 0 && errno == EINTR) { if (exitRequested == 1) { free(buffer); break; } } // 超时或者没有数据,回到循环开头检查exitRequested
额外注意事项
- 你原来的代码中
getline()分配的buffer没有释放,会存在内存泄漏,需要补充释放逻辑。 - 信号处理函数中只能调用异步信号安全的函数,不要使用
printf()、malloc()这类非安全函数,避免出现未定义行为。 - 原来的main函数中
pthread_join()会阻塞等待子线程退出,后面的无限循环在线程退出前不会执行,如果你需要main和子线程同时跑业务逻辑,需要调整pthread_join()的位置。
内容的提问来源于stack exchange,提问作者Alex Hu
相关产品推荐
相关产品推荐

