c++怎么在linux下处理信号_c++ signal函数绑定与优雅退出【详解】

11次阅读

应弃用 signal() 改用 sigaction():前者行为不可靠,可能重置信号、丢失信号、无法控制掩码和系统调用重启;后者可精确设置 sa_mask、SA_RESTART,并要求仅调用异步信号安全函数。

c++怎么在linux下处理信号_c++ signal函数绑定与优雅退出【详解】

signal() 函数在 linux 下的绑定行为很不可靠

Linux 下直接用 signal() 绑定信号处理函数,基本等于“听天由命”——它在不同系统(尤其是 glibc 版本差异)下语义不一致:signal() 可能重置为默认行为、可能不阻塞同信号递归、还可能丢失信号。POSIX 明确推荐弃用它,改用 sigaction()

  • signal(SIGint, handler) 在某些 glibc 版本中注册后,收到一次 SIGINT 就自动恢复成 SIG_DFL,第二次 Ctrl+C 直接终止进程
  • 无法控制信号掩码,处理期间若再收到同信号(如快速连按 Ctrl+C),可能被忽略或引发未定义行为
  • 不能指定是否重启被中断的系统调用(SA_RESTART),导致 read()accept() 等意外返回 EINTR

用 sigaction() 实现可重入、可屏蔽、可重启的信号处理

真正可控的方式是 sigaction(),它能精确控制信号行为。关键点:显式清空 sa_mask、设置 SA_RESTART、避免使用非异步信号安全函数(如 printfmalloc)。

  • 必须用 sigemptyset(&sa.sa_mask) 初始化信号掩码,否则行为未定义
  • 加上 SA_RESTART 标志,让被中断的系统调用自动重试,避免满屏 EINTR
  • 处理函数里只能调用异步信号安全函数(write()_exit()sigprocmask() 等),std::coutstd::String 都不安全
struct sigaction sa; sa.sa_handler = [](int sig) {     write(STDERR_FILENO, "Caught SIGINTn", 16);     _exit(1);  // 不要用 exit() — 它不是 async-signal-safe }; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sigaction(SIGINT, &sa, nullptr);

如何实现“优雅退出”:全局标志 + 主循环检查

信号处理函数不能做复杂逻辑,真正的清理工作必须回到线程。通用做法是用 volatile sig_atomic_t 做原子标志位,主循环定期检查并执行析构、关闭资源等操作。

  • 必须用 volatile sig_atomic_t(而非 boolint),确保读写不被编译器优化掉,且是原子访问类型
  • 不要在信号处理函数中调用 close()deletestd::Thread::join() 等——它们都不在 async-signal-safe 列表里
  • 主循环应避免长时间阻塞(如无超时的 sleep()),可用 poll()epoll_wait() 带超时来轮询标志位
volatile sig_atomic_t g_exit_requested = 0;  void signal_handler(int sig) {     g_exit_requested = 1; }  // 注册 struct sigaction sa; sa.sa_handler = signal_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sigaction(SIGTERM, &sa, nullptr); sigaction(SIGINT,  &sa, nullptr);  // 主循环 while (!g_exit_requested) {     // do_work();     poll(nullptr, 0, 100); // 每 100ms 检查一次 } // 此处执行 close(), delete, join() 等安全清理

多线程环境下信号处理要格外小心

Linux 中信号是发给整个进程的,但最终由某个线程接收。默认情况下,任何线程都可能收到信号,导致行为难以预测。若想集中处理,必须显式屏蔽所有线程的信号,只在专用线程中 sigsuspend() 等待。

立即学习C++免费学习笔记(深入)”;

  • 主线程创建子线程前,先用 pthread_sigmask() 屏蔽所有关心的信号
  • 开一个专用信号处理线程,用 sigsuspend()sigwait() 同步等待信号
  • 绝对不要在多个线程里分别调用 signal()sigaction() —— 行为未定义
  • std::signal()c++11 起)在多线程中效果与 C 的 signal() 相同,同样不推荐

信号处理本身不是难题,难的是理解哪些操作真正安全、哪些只是“碰巧没崩”。很多人用 signal()exit() 跑了一年没出事,直到某次高负载下信号丢失或重入,服务静默崩溃——那才是最麻烦的。

text=ZqhQzanResources