文章目录
1. 问题现象
- 现象1: CTRL+C 无法退出正在运行的C++多线程ROS程序
- 现象2:CTRL+Z 强制结束进程后top查看仍然有进程残留
- 现象3:通过 kill -9 pid 彻底结束ROS进程
2. 问题原因
- 在linux程序中,当用户按下CTRL+C命令后,程序可能接收到SIGINT中断信号(或 SIGTERM终止信号),但是如果程序正在执行多线程程序等,SIGINT信号可能被忽略或阻止,可能只有当前接收到SIGINT的子线程会退出,但是程序并不能优雅地停止
- CTRL+Z发送SIGSTOP暂停信号,进程从前台转入后台并暂停,任务并没有结束只是维持挂起状态
- kill -9发送SIGKILL终止信号,即exit信号,该信号不会被系统阻塞,能立即终止进程即子进程,因而能顺利退出程序,但是被杀死的进程没有机会告诉其父进程它已收到终止信号,可能导致创建僵尸进程
3. 解决方案
- 可以通过设置信号处理函数来进行处理,以便在接收到SIGINT信号时清理资源并退出程序
- 设置信号处理函数,确保主线程能够接收到并处理这个信号,从而实现优雅地退出
4. 代码实现与测试
-
利用signal()函数将SignalHandler注册为SIGINT或SIGTERM信号处理程序,停止ros spin循环
#include <signal.h> void SignalHandler(int signal) { // 还可以停止ros spinner对象,如果不用可注释掉 spinner->stop(); // 1. 向其他ros节点publish停止信号 node_stop_msg = xxx your_pub.publish(node_stop_msg); // 2. 节点内如果有控制线程运行的标志位,如atomic<bool> keep_threads_running; keep_threads_running = false; // 3. 默认调用shutdown函数,通知ROS节点安全的关闭,导致ros::spin() / rclcpp::spin()退出 ros::shutdown(); // ROS 1 rclcpp::shutdown(); // ROS 2 } // ROS 1 主程序调用示例 int main(int argc, char** argv) { ros::init(argc, argv, "your_node_name", ros::init_options::NoSigintHandler); ros::NodeHandle nh; // This must be set after the first NodeHandle is created. signal(SIGINT, SignalHandler); // 重载默认的signal handler函数 signal(SIGTERM, SignalHandler); // SIGTERM信号也可添加处理函数 ros::spin(); return 0; } // ROS 2主程序调用示例 int main(int argc, char** argv) { rclcpp::init(argc, argv); rclcpp::executors::SingleThreadedExecutor executor; auto your_node = std::make_shared<YourNodeClass>(); executor.add_node(your_node->get_node_base_interface()); std::thread your_sub_thread(xxx); signal(SIGINT, SignalHandler); signal(SIGTERM, SignalHandler); executor.spin(); your_sub_thread.join(); // 关卡 rclcpp::shutdown(); return 0; }
创作不易,如有帮助,请 点赞 收藏 支持
[参考文章]
[1]. SIGTERM和SIGKILL信号的区别
[2]. ctrl c退出ros程序
[3]. ros c++程序signal函数功能和实现
created by shuaixio, 2024.04.15