ROS的多线程2-调度机制

 先来说下ros的回调-callback

ROS 共有四种类型的回调,分别绑在不同的对象上:

订阅回调:ros::subscriber

请求回调:ros::servivceserver

行动回调:ros::simpleactionserver

定时器回调:ros::Timer

而callback 一般是在ros框架下,我们来写的,为了实现自己的某种功能的实现。

OS回调机制

ROS默认有维护一个全局回调队列(名为:Global Callback Queue),将已可用的回调插入Callback队列中。再通过Spinner线程获取并执行当前可用的回调。为了说明ROS回调机制,我引入两个ROS节点:一个使用定时器发布多个topic消息;另一个订阅这些topic消息。

发布节点

一个使用ROS定时器超时回调发布多个topic消息。

ros::Timer

来看一个ros::Timer的回调队列,代码如下:

//这段代码主要是实现定时向Topic发布消息
#include "ros/ros.h"
#include <boost/thread.hpp>
#include "my_msg/weather_pub.h"
#include "my_msg/air_quality_pub.h"
#include <sstream>

int main(int argc, char **argv){
  ros::init(argc, argv, "multi_publisher");
  ros::NodeHandle n;
  /*通知ROS master,本node要发布一个名为“Weather”的话题(topic),
  消息类型为my_msg::weather_pub,发送队列长度为48*/
  ros::Publisher pub_weather = 
    n.advertise<my_msg::weather_pub>("Weather", 48, true);

  /*通知ROS master,本node要发布一个名为“WeatherA”的话题(topic),
  消息类型为my_msg::weather_pub,发送队列长度为48*/
  ros::Publisher pub_weather_a = 
    n.advertise<my_msg::weather_pub>("WeatherA", 48, true);

  /*通知ROS master,本node要发布一个名为“AirQuality”的话题(topic),
  消息类型为my_msg::air_quality_pub,发送队列长度为48*/
  ros::Publisher pub_air_quality = 
    n.advertise<my_msg::air_quality_pub>("AirQuality", 48, true);

  int count = 0;
  //创建一个ros::Timer每0.2秒进行发布,回调函数采用lamda4方法的格式
  ros::Timer timer = n.createTimer(ros::Duration(0.2),
  [&](const ros::TimerEvent&) {

    my_msg::weather_pub msg;

    std::stringstream ss;
    ss << "Sunny " << count;
    msg.weather= ss.str();
    ROS_INFO_STREAM("Thread["<< boost::this_thread::get_id()
      <<"],weather:"<< msg.weather.c_str());
    pub_weather.publish(msg);

    std::stringstream ssa;
    ssa << "Sunny " << 20+count;
    msg.weather= ssa.str();
    ROS_INFO_STREAM("Thread["<< boost::this_thread::get_id()
      <<"],weather:"<< msg.weather.c_str());
    pub_weather_a.publish(msg);

    my_msg::air_quality_pub msg_air;
    msg_air.air_quality_index = 128+count;
    ROS_INFO_STREAM("Thread["<< boost::this_thread::get_id()
      <<"],air quality:"<< msg_air.air_quality_index);
 
<think>嗯,用户想了解ROS2节点多线程处理的方法。这个问题很实用,因为在实际机器人系统中,多线程处理对性能优化至关重要。从用户提供的引用来看,他/她应该已经查阅过相关资料,但需要更系统的解决方案。 用户提到的引用[1]指出ROS本身资源消耗大,多传感器时间戳不同步问题,这确实是痛点。引用[2][3]展示了多线程执行器的实际效果和线程ID验证,说明用户关注的是具体实现而非理论。 我注意到几个关键技术点需要覆盖: 第一层是执行器类型选择,MultiThreadedExecutor是核心方案,必须详细说明其线程调度机制。第二层要解释回调组的重要概念,特别是互斥组和可重入组的区别,这是容易混淆的点。第三层需要给出代码模板,特别是rclcpp::NodeOptions的配置方法这个易错点。 用户可能遇到的实际问题是:如何避免高频数据丢失?这点在引用[1]中提到过,解决方案在于回调组的合理配置。我会强调在数据采集节点使用MutuallyExclusiveGroup保证数据完整性,在处理节点使用ReentrantGroup提升效率。 考虑到用户可能正在开发实际系统,除了基础示例外,还需要提醒资源限制问题。比如线程数上限在Linux系统默认是1024,但实际设置超过CPU核心数反而会降低性能。这个细节虽然不在引用里,但对工程实现很重要。 最后要区分普通线程和ROS线程的使用场景。像引用[3]展示的纯计算任务适合用std::thread,但涉及话题订阅的必须用ROS多线程机制。这个边界必须说清楚,否则用户可能误用。 </think>在ROS2中实现节点多线程处理主要依靠**执行器(Executor)**和**回调组(CallbackGroup)**机制,以下是核心方法及示例: --- ### **1. 多线程执行器 (MultiThreadedExecutor)** 核心原理:创建线程池并行处理回调函数 ```cpp #include "rclcpp/rclcpp.hpp" class MultiThreadNode : public rclcpp::Node { public: MultiThreadNode() : Node("multi_thread_node") { // 创建两个订阅(实际场景可能是传感器回调) sub1_ = create_subscription<std_msgs::msg::String>("topic1", 10, [this](const std_msgs::msg::String::SharedPtr msg) { RCLCPP_INFO(this->get_logger(), "回调1执行,线程ID: %ld", std::this_thread::get_id()); std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作 }); sub2_ = create_subscription<std_msgs::msg::String>("topic2", 10, [this](const std_msgs::msg::String::SharedPtr msg) { RCLCPP_INFO(this->get_logger(), "回调2执行,线程ID: %ld", std::this_thread::get_id()); std::this_thread::sleep_for(std::chrono::seconds(1)); }); } private: rclcpp::Subscription<std_msgs::msg::String>::SharedPtr sub1_, sub2_; }; int main(int argc, char** argv) { rclcpp::init(argc, argv); auto node = std::make_shared<MultiThreadNode>(); // 关键:创建多线程执行器(默认线程数=CPU核心数) rclcpp::executors::MultiThreadedExecutor executor; executor.add_node(node); executor.spin(); // 自动分配线程处理回调 rclcpp::shutdown(); return 0; } ``` **效果**: - 回调1和回调2在不同线程中**并发执行** - 总耗时由最长回调决定(约2秒)[^2] --- ### **2. 回调组 (CallbackGroup)** 用于控制回调的执行方式,结合`MultiThreadedExecutor`使用: #### **2.1 互斥回调组 (MutuallyExclusiveGroup)** 组内回调**串行执行**,避免资源竞争 ```cpp // 在节点构造函数中添加: auto mutex_group = create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive); sub1_ = create_subscription<...>(..., rclcpp::SubscriptionOptions(), // 关键参数 mutex_group); // 指定回调组 ``` #### **2.2 可重入回调组 (ReentrantGroup)** 组内回调**并行执行**(需线程安全) ```cpp auto reentrant_group = create_callback_group(rclcpp::CallbackGroupType::Reentrant); timer_ = create_wall_timer(..., reentrant_group); // 定时器回调可并行 ``` --- ### **3. 高级配置** #### **3.1 自定义线程数** ```cpp rclcpp::executors::MultiThreadedExecutor executor( rclcpp::ExecutorOptions(), 4 // 明确指定4个工作线程 ); ``` #### **3.2 混合使用标准线程** ```cpp // 在回调函数中启动独立线程 std::thread([this](){ // 执行非ROS相关的耗时计算 }).detach(); ``` --- ### **关键选择策略** | **场景** | **推荐方案** | |---------------------------|----------------------------------| | 高频传感器数据处理 | `MultiThreadedExecutor` + `ReentrantGroup` | | 需要严格顺序的操作 | `MutuallyExclusiveGroup` | | CPU密集型计算 | 回调内启动`std::thread` | > **注意**: > 1. 使用`ReentrantGroup`时需确保回调函数**线程安全** > 2. 避免在回调中执行阻塞操作,否则会阻塞线程池[^1] > 3. 线程数并非越多越好,建议不超过CPU物理核心数 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值