关机或重启崩溃主因是信号处理不安全及多线程资源清理竞态:主线程退出后工作线程仍访问已析构全局对象,导致野指针或锁销毁后仍被调用;须验证sigterm来源、检查析构顺序、禁用非异步信号安全函数,并确保线程正确join。

关机或重启时崩溃,基本不是“系统太急”,而是程序在收到 SIGTERM 或 SIGHUP 后,多线程资源清理逻辑存在竞态、析构顺序错乱或信号处理不安全——尤其当主线程已退出、工作线程仍在访问全局对象或单例时,this 指针立刻变野。
确认是否真由关机信号触发
别假设是“系统杀进程导致崩溃”,先验证信号来源。Linux 下关机流程通常向进程组发送 SIGTERM(默认 10 秒后补 SIGKILL),而非直接强杀:
- 在
main()开头注册信号处理器:signal(SIGTERM, sigterm_handler),并在 handler 中调用abort()或raise(SIGTRAP)让 gdb 捕获 - 运行时加环境变量
export DEBUG_SIGNALS=1,在 handler 里打印backtrace_symbols_fd()到文件,确认崩溃前是否真进了 signal handler - 检查 systemd 日志:
journalctl -u your-service --since "1 hour ago",看是否有 “Stopping …” 后紧跟 “Process … (PID) dumped core” - 若用
systemd管理服务,确认KillMode是control-group(默认)还是mixed:前者会同时发信号给所有线程,后者只发给主线程——这直接影响你看到的崩溃线程 ID
排查线程析构与全局对象生命周期冲突
关机时最典型的崩溃模式是:主线程退出,触发静态/全局对象析构,但 worker 线程还在跑,访问已被销毁的 std::shared_ptr、单例或 static std::mutex —— 此时 gdb bt 常卡在 __pthread_mutex_lock 或虚函数调用(vptr 已被覆写):
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 禁用所有全局/静态对象的析构副作用:把单例改为
static T* instance = nullptr;+ 手动delete,避免依赖 atexit - 检查所有
std::thread是否都join()或detach():未join()的线程在主线程 return 后会触发std::terminate,报SIGABRT,堆栈顶是std::thread::~_State - 用
valgrind --tool=helgrind ./your_program运行模拟关机(kill -TERM $(pidof your_program)),helgrind 会明确报 “lock is destroyed while still locked” 或 “data race on global variable” - 在关键对象析构函数开头加
assert(!std::this_thread::get_id().operator==({}));(检测是否在非主线程析构),能快速暴露误在 worker 线程析构全局资源的问题
避免信号处理中调用非异步信号安全函数
很多教程教你在 SIGTERM handler 里直接 std::cout 或 <code>close(fd),这是危险的——std::cout、malloc、printf 全部不是 async-signal-safe,若信号恰好在 malloc 中间被投递,会直接死锁或崩溃:
- handler 内只允许调用 async-signal-safe 函数:如
write()、sigprocmask()、_exit();禁止用std::string、std::vector、std::mutex、任何 STL 容器 - 正确做法:handler 只写一个标志位(
volatile sig_atomic_t g_shutdown_requested = 0;),然后由主线程轮询该变量,再走正常 shutdown 流程 - 若必须记录日志,用
write(STDERR_FILENO, "TERM received\n", 16)这类裸系统调用,且确保字符串字面量在 .rodata 段,不涉及动态内存 - 检查是否用了
std::signal而非sigaction:后者可设sa_mask阻塞其他信号,防止嵌套信号干扰
关机崩溃最难复现的点在于:它依赖精确的线程退出时机和全局对象析构顺序,而这两者在不同 libc 版本、不同优化等级下行为不一致。不要靠加 sleep(1) 来“稳定复现”,那只是掩盖了真正的析构竞态——盯住 SIGTERM 处理路径、全局对象生存期、以及所有未 join() 的线程,这三个交点才是根因所在。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










