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

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()操作,通用性更强。

核心思路:

  1. 创建一个匿名管道;
  2. 信号处理函数向管道写入一个字节(无需关心内容,仅作为唤醒标志);
  3. 主线程用select()/poll()/epoll()等待管道可读,实现被动挂起;
  4. 管道可读时,读取字节并执行后续逻辑。

实现代码:

#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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 15:50:15