如何通过SIGTERM信号安全终止线程并返回主函数?
多线程CPU使用率程序SIGTERM安全终止问题解决
问题概述
编写多线程CPU使用率计算程序时,无法通过SIGTERM信号安全终止线程。尝试过while(flag)循环、pthread_exit()、exit()、return(void*)0等方法,要么导致整个程序退出,要么程序停滞无响应,需要终止线程的无限循环并返回主函数。
代码中的核心问题
主线程
pthread_join逻辑错误
原代码中主线程的pthread_join循环重复三次调用所有线程的join,第一次pthread_join(thread[0], NULL)就会阻塞到reader线程结束,而reader线程默认处于while(flag)循环(flag初始为1),导致主线程长期阻塞,后续的pthread_cancel代码根本无法执行。线程阻塞时无法响应flag变化
线程可能卡在sem_wait或sleep调用中,即使SIGTERM触发将flag设为0,线程也无法及时检测到并退出循环。例如:sleep会阻塞线程,信号中断sleep后,线程会继续执行循环,但如果没在循环开头及时检查flag,可能继续执行后续逻辑;sem_wait被信号中断时会返回EINTR错误,原代码未处理该错误,线程会再次调用sem_wait,陷入阻塞。
不必要的
sleep导致响应延迟
代码中添加的sleep(1)完全多余,不仅降低程序效率,还会延迟线程对flag变化的响应。线程退出时未正确处理信号量
当flag设为0后,线程可能卡在信号量等待中,无法退出,导致主线程join时阻塞。
修复方案
1. 修正主线程逻辑
- 主线程不应一开始就阻塞
join,而是先进入暂停状态(用pause()),直到SIGTERM信号处理后,再依次join所有线程,无需调用pthread_cancel。 - 移除冗余的
pthread_cancel代码,通过flag控制线程退出是更安全的方式。
2. 让线程及时响应flag变化
- 在循环的关键节点(如
sem_wait前后)检查flag,确保线程能及时退出; - 处理
sem_wait的EINTR错误,当信号中断sem_wait时,重新检查flag,避免无限阻塞。
3. 移除不必要的sleep
- 删除所有
sleep(1)调用,提升程序响应速度。
4. 安全处理信号量
- 在信号处理函数中主动唤醒所有信号量,确保阻塞的线程能被唤醒;
- 线程退出循环前释放对应的信号量,避免其他线程卡在等待中。
修复后的完整代码
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <pthread.h> #include <signal.h> #include <ctype.h> #include <unistd.h> #include <sys/sysinfo.h> #include <malloc.h> #include <time.h> #include <semaphore.h> #include <errno.h> #include "cpudata.h" #include "cpu_result.h" #include "reader.h" #include "analyzer.h" #include "writer.h" #define NUM_THREADS 3 // 用sig_atomic_t确保信号处理中对flag的修改是原子操作 volatile sig_atomic_t flag = 1; volatile struct cpu_data *datax1; volatile struct cpu_data *datax2; volatile struct cpu_result *result; sem_t sem1; sem_t sem2; sem_t sem3; volatile int val[NUM_THREADS] = {0, 0, 0}; void handle_sigterm(int signum) { flag = 0; // 唤醒所有阻塞在sem_wait的线程 sem_post(&sem1); sem_post(&sem2); sem_post(&sem3); } void *reader(void *ptr) { while (flag) { sem_post(&sem3); // 处理sem_wait被信号中断的情况 int ret; while ((ret = sem_wait(&sem1)) == -1 && errno == EINTR) { if (!flag) break; } if (!flag) break; load_data(datax1); load_data(datax2); if (memcmp(datax1, datax2, (NOPT+1)*sizeof(struct cpu_data)) == 0) { perror("Datas are the same"); } } // 退出前释放信号量,避免其他线程阻塞 sem_post(&sem3); sem_post(&sem1); pthread_exit((void*)&val[0]); } void *analyzer(void *ptr) { while (flag) { sem_post(&sem1); int ret; while ((ret = sem_wait(&sem2)) == -1 && errno == EINTR) { if (!flag) break; } if (!flag) break; for (int i = 0; i < NOPT+1; i++) { result[i] = calculate(datax1[i], datax2[i]); } } sem_post(&sem1); sem_post(&sem2); pthread_exit((void*)&val[1]); } void *writer(void *ptr) { while (flag) { sem_post(&sem2); int ret; while ((ret = sem_wait(&sem3)) == -1 && errno == EINTR) { if (!flag) break; } if (!flag) break; writeresult(result); printf("\n\n\n\n\n\n"); } sem_post(&sem2); sem_post(&sem3); pthread_exit((void*)&val[2]); } int main(int argc, char **argv) { struct sigaction action; memset(&action, 0, sizeof(struct sigaction)); action.sa_handler = handle_sigterm; sigaction(SIGTERM, &action, NULL); // 同时处理SIGINT(Ctrl+C)方便测试 sigaction(SIGINT, &action, NULL); // 分配内存 datax1 = (struct cpu_data *)malloc((NOPT+1)*sizeof(struct cpu_data)); datax2 = (struct cpu_data *)malloc((NOPT+1)*sizeof(struct cpu_data)); result = (struct cpu_result *)malloc((NOPT+1)*sizeof(struct cpu_result)); pthread_t thread[NUM_THREADS]; // 调整信号量初始值,匹配reader→analyzer→writer的工作流 sem_init(&sem1, 0, 1); sem_init(&sem2, 0, 0); sem_init(&sem3, 0, 0); // 创建线程 pthread_create(&thread[0], NULL, &reader, NULL); pthread_create(&thread[1], NULL, &analyzer, NULL); pthread_create(&thread[2], NULL, &writer, NULL); // 主线程暂停,等待信号触发 pause(); // 等待所有线程退出 pthread_join(thread[0], NULL); pthread_join(thread[1], NULL); pthread_join(thread[2], NULL); // 清理资源 sem_destroy(&sem1); sem_destroy(&sem2); sem_destroy(&sem3); free((void*)datax1); free((void*)datax2); free((void*)result); printf("SAFETY CLOSING\n"); return 0; }
关键修复点说明
sig_atomic_t类型的flag:确保信号处理函数中对flag的修改是原子操作,避免多线程竞态条件。- 信号处理函数唤醒信号量:主动触发
sem_post,让所有阻塞的线程能退出等待,检查flag后终止。 - 处理
sem_wait的EINTR错误:信号中断等待时,重新检查flag,避免线程无限阻塞。 - 调整信号量初始值:让sem2和sem3初始为0,符合线程间的依赖顺序,避免启动时的无效操作。
- 主线程
pause()等待信号:不再提前阻塞join,确保主线程能及时响应信号,触发退出逻辑。
内容的提问来源于stack exchange,提问作者ulanny
相关产品推荐
相关产品推荐

