

新闻资讯
技术学院应弃用 signal() 改用 sigaction():前者行为不可靠,可能重置信号、丢失信号、无法控制掩码和系统调用重启;后者可精确设置 sa_mask、SA_RESTART,并要求仅调用异步信号安全函数。
Linux 下直接用 signal() 绑定信号处理函数,基本等于“听天由命”——它在不同系统(尤其是 glibc 版本差异)下语义不一致:signal() 可能重置为默认行为、可能不阻塞同信号递归、还可能丢失信号。POSIX 明确推荐弃用它,改用 sigaction()。
signal(SIGINT, handler) 在某些 glibc 版本中注册后,收到一次 SIGINT 就自动恢复成 SIG_DFL,第二次 Ctrl+C 直接终止进程SA_RESTART),导致 read()、accept() 等意外返回 EINTR
真正可控的方式是 sigaction(),它能精确控制信号行为。关键点:显式清空 sa_mask、设置 SA_RESTART、避免使用非异步信号安全函数(如 printf、malloc)。
sigemptyset(&sa.sa_mask) 初始化信号掩码,否则行为未定义SA_RESTART 标志,让被中断的系统调用自动重试,避免满屏 EINTR
write()、_exit()、sigprocmask() 等),std::cout 和 std::string 都不安全struct sigaction sa;
sa.sa_handler = [](int sig) {
write(STDERR_FILENO, "Caught SIGINT\n", 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(而非 bool 或 int),确保读写不被编译器优化掉,且是原子访问类型close()、delete、std::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() 跑了一年没出事,直到某次高负载下信号丢失或重入,服务静默崩溃——那才是最麻烦的。