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

macOS下异步信号安全唤醒线程方案(C/C++ GRPC服务场景)

在macOS上实现GRPC服务器的优雅终止(信号安全唤醒)

核心方案:自管道技巧(Self-Pipe Trick)

这是跨Unix系统(包括macOS)的经典信号安全唤醒方案,完全符合异步信号安全要求,无需轮询。原理是利用管道的可读事件触发线程唤醒,而write()属于macOS官方列出的异步信号安全函数,可在信号处理程序中安全调用。

实现步骤

  • 创建管道:程序初始化时创建匿名管道,保存读端和写端文件描述符。
  • 注册信号处理函数:为SIGINT和SIGTERM注册处理函数,函数内部仅向管道写端写入1字节(无需关心内容,仅用于触发可读事件)。
  • 监听管道事件:主线程(或负责等待终止的线程)使用kevent(macOS原生推荐,也可用select/poll)监听管道读端的可读事件,一旦触发,启动GRPC服务器的优雅关闭流程。

代码示例

#include <unistd.h>
#include <signal.h>
#include <sys/event.h>
#include <grpcpp/grpcpp.h>

// 全局管道文件描述符(需保证在信号处理前初始化)
int termination_pipe[2];

void signal_handler(int sig) {
    // 向管道写一个字节,触发可读事件
    char dummy = 'x';
    // 忽略EINTR,写入1字节不会阻塞
    (void)write(termination_pipe[1], &dummy, 1);
}

int main(int argc, char** argv) {
    // 初始化管道
    if (pipe(termination_pipe) == -1) {
        perror("pipe failed");
        return 1;
    }

    // 设置信号处理函数
    struct sigaction sa;
    sa.sa_handler = signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART; // 重启被信号打断的系统调用
    if (sigaction(SIGINT, &sa, nullptr) == -1 || sigaction(SIGTERM, &sa, nullptr) == -1) {
        perror("sigaction failed");
        close(termination_pipe[0]);
        close(termination_pipe[1]);
        return 1;
    }

    // 初始化GRPC服务器
    grpc::ServerBuilder builder;
    // 注册服务...
    std::unique_ptr<grpc::Server> server = builder.BuildAndStart();
    if (!server) {
        perror("server start failed");
        close(termination_pipe[0]);
        close(termination_pipe[1]);
        return 1;
    }

    // 设置kevent监听管道读端
    int kq = kqueue();
    if (kq == -1) {
        perror("kqueue failed");
        server->Shutdown();
        close(termination_pipe[0]);
        close(termination_pipe[1]);
        return 1;
    }

    struct kevent event;
    EV_SET(&event, termination_pipe[0], EVFILT_READ, EV_ADD, 0, 0, nullptr);
    if (kevent(kq, &event, 1, nullptr, 0, nullptr) == -1) {
        perror("kevent register failed");
        server->Shutdown();
        close(kq);
        close(termination_pipe[0]);
        close(termination_pipe[1]);
        return 1;
    }

    // 等待终止信号触发的管道事件
    struct kevent triggered_event;
    int ret = kevent(kq, nullptr, 0, &triggered_event, 1, nullptr);
    if (ret == -1) {
        perror("kevent wait failed");
    }

    // 启动GRPC优雅关闭
    server->Shutdown();
    // 等待所有请求处理完成
    server->Wait();

    // 清理资源
    close(kq);
    close(termination_pipe[0]);
    close(termination_pipe[1]);
    return 0;
}

关于os_unfair_lock的说明

苹果官方文档未将os_unfair_lock标记为异步信号安全,因此绝对不能在信号处理程序中调用os_unfair_lock_lock()或os_unfair_lock_unlock()——这类操作可能导致死锁(若信号触发时锁已被持有)或其他未定义行为,完全不适合用于信号上下文的同步。

关键注意事项

  • 信号安全函数限制:信号处理程序中只能调用macOS官方列出的异步信号安全函数(可通过man 7 signal查看),绝对不能调用printf()、malloc()等非线程安全函数。
  • 管道缓冲区:写入1字节足够触发事件,管道默认缓冲区远大于1字节,不会出现阻塞情况,无需处理写入失败(SA_RESTART已帮助重启系统调用)。
  • GRPC优雅关闭:务必先调用server->Shutdown()停止接收新请求,再调用server->Wait()等待现有请求处理完成,确保优雅终止。

内容的提问来源于stack exchange,提问作者Imanol Barba Sabariego

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 17:23:36