You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过SIGTERM信号安全终止线程并返回主函数?

多线程CPU使用率程序SIGTERM安全终止问题解决

问题概述

编写多线程CPU使用率计算程序时,无法通过SIGTERM信号安全终止线程。尝试过while(flag)循环、pthread_exit()、exit()、return(void*)0等方法,要么导致整个程序退出,要么程序停滞无响应,需要终止线程的无限循环并返回主函数。

代码中的核心问题

  1. 主线程pthread_join逻辑错误
    原代码中主线程的pthread_join循环重复三次调用所有线程的join,第一次pthread_join(thread[0], NULL)就会阻塞到reader线程结束,而reader线程默认处于while(flag)循环(flag初始为1),导致主线程长期阻塞,后续的pthread_cancel代码根本无法执行。

  2. 线程阻塞时无法响应flag变化
    线程可能卡在sem_wait或sleep调用中,即使SIGTERM触发将flag设为0,线程也无法及时检测到并退出循环。例如:

    • sleep会阻塞线程,信号中断sleep后,线程会继续执行循环,但如果没在循环开头及时检查flag,可能继续执行后续逻辑;
    • sem_wait被信号中断时会返回EINTR错误,原代码未处理该错误,线程会再次调用sem_wait,陷入阻塞。
  3. 不必要的sleep导致响应延迟
    代码中添加的sleep(1)完全多余,不仅降低程序效率,还会延迟线程对flag变化的响应。

  4. 线程退出时未正确处理信号量
    当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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 20:54:28