ROS C++ : 使用ros::AsyncSpinner,实现多线程处理ROS消息

news2024/10/4 18:43:05

文章目录

    • 1、原理说明
      • 1.1、ros::MultiThreadedSpinner
      • 1.2、ros::AsyncSpinner
      • 1.3、多线程原理
        • 1.3.1、 消息发布
        • 1.3.2、 消息订阅
    • 2、ros::AsyncSpinner 示例1
    • 3、ros::AsyncSpinner 示例2
    • 4、使用 ros::AsyncSpinner, 多线程处理回调示例

1、原理说明

ROS提供了2中方式支持多个线程调用回调函数:使用ros::MultiThreadedSpinner和ros::AsyncSpinner.

  • ros::MultiThreadedSpinner——相当于while(true)大循环,启动指定数量的Spinner线程并发执行Callback队列中的可用回调。可指定Callback队列。
  • ros::AsyncSpinner——异步启停,开启指定数量的Spinner线程并发执行Callback队列中的可用回调。可指定Callback队列。

1.1、ros::MultiThreadedSpinner

ros::MultiThreadSpinner是一个阻塞式的spin,类似于ros::spin()。但可以在它的构造函数中指定线程数,如果没有指定(或设置为0),它将为每个CPU核心使用一个线程。


ros::MultiThreadedSpinner spinner(4); // Use 4 threads
spinner.spin(); // spin() will not return until the node has been shutdown

1.2、ros::AsyncSpinner

一个更有用的线程微调器是AsyncSpinner。它不是一个阻塞的spin()调用,而是调用start()和stop(),并且会在它被销毁时自动停止。AsyncSpinner与上面MultiThreadedSpinner示例的等效用法是:


ros::AsyncSpinner spinner(4); // Use 4 threads
spinner.start();
ros::waitForShutdown(); // Wait for this node to be shutdown, whether through Ctrl-C, ros::shutdown(), or similar. 

请注意,ros::waitForShutdown()函数不会自己旋转,因此上面的示例总共会旋转4个线程。

1.3、多线程原理

1.3.1、 消息发布

ros::Publisher将要发布的消息加入到Publisher队列中,再由专门的Publisher线程发布出去。注意这其中并不涉及Callback队列:

在这里插入图片描述

1.3.2、 消息订阅

ROS默认有维护一个全局回调队列(名为:Global Callback Queue),将已可用的回调插入Callback队列中。再通过Spinner线程获取并执行当前可用的回调。

定时器启动后会生成一个Timer线程,根据定时器的参数,当定时器超时后将定时器的回调函数加入Callback队列中。然后再由用户调用的Spinner线程(ros::spin)从Callback队列中依次取出当前已可用的回调并执行。

在这里插入图片描述

2、ros::AsyncSpinner 示例1

下面这段代码展示如何使用ros::AsyncSpinner启用多个Spinner线程。


//一个topic多个线程来执行的代码
#include "ros/ros.h"
#include "boost/thread.hpp"
#include "my_msg/weather_pub.h"

//回调函数,注意参数是const类型的boost::shared_ptr指针
void weatherCallback(const my_msg::weather_pubConstPtr& msg)
{
  ROS_INFO_STREAM("Thread["<< boost::this_thread::get_id()
    <<"],after looping 24 hours weather:"<< msg->weather.c_str());
}

int main(int argc, char **argv){
  ros::init(argc, argv, "multi_subscriber");
  ros::NodeHandle n;
  /*通知ROS master,本node要订阅名为“Weather”的话题(topic),
  并指定回调函数weatherCallback*/
  ros::Subscriber sub = n.subscribe("Weather", 48, weatherCallback);

  ROS_INFO_STREAM("Thread["<< boost::this_thread::get_id()
    <<"]This is main thread.");

  //声明spinner对象,参数2表示并发线程数,默认处理全局Callback队列
  ros::AsyncSpinner spinner(2);
  //启动两个spinner线程并发执行可用回调  
  spinner.start();
  ros::waitForShutdown();
}



从执行结果中可以看到,进程中包括三个线程:主线程、Spinner线程1、Spinner线程2。


//这是执行结果,可以看到主线程
[ INFO] [1637131602.089381910]: Thread[7f9a1ad24780]This is main thread.
[ INFO] [1637131602.375058712]: Thread[7f9a11bb6700],after looping 24 hours weather:Sunny 679
[ INFO] [1637131602.488504089]: Thread[7f9a11bb6700],after looping 24 hours weather:Sunny 680
[ INFO] [1637131602.688845441]: Thread[7f9a123b7700],after looping 24 hours weather:Sunny 681
[ INFO] [1637131602.888828136]: Thread[7f9a123b7700],after looping 24 hours weather:Sunny 682

下图展示了相关的线程和队列处理过程 :

在这里插入图片描述

3、ros::AsyncSpinner 示例2

实际项目中一个节点往往要订阅多个topic,在使用默认全局Callback队列时,如果某些topic发布频率高回调处理又耗时的话,容易影响其他topic消息的处理。下图中TopicB的消息巨多可能影响TopicA的处理。

在这里插入图片描述这种情况下,ROS提供了机制,可以为每个ros::Subscriber指定Callback队列,再分别指定Spinner线程仅处理指定Callback队列的回调。这样确保每个订阅回调相互独立不影响。下面的代码展示如何进行上述操作:


//为每个subscriber指定队列
#include "ros/ros.h"
#include "boost/thread.hpp"
#include "my_msg/weather_pub.h"
#include "my_msg/air_quality_pub.h"
#include <ros/callback_queue.h>

//回调函数,注意参数是const类型的boost::shared_ptr指针
void weatherCallback(const my_msg::weather_pubConstPtr& msg)
{
  ROS_INFO_STREAM("Thread["<< boost::this_thread::get_id()
    <<"],before loop 24 hours weather:"<< msg->weather.c_str()); 
  //死循环
  while(true){}

  ROS_INFO_STREAM("Thread["<< boost::this_thread::get_id()
    <<"],24 hours weather:"<< msg->weather.c_str()); 
}

void weatherCallback_A(const my_msg::weather_pubConstPtr& msg)
{
  ROS_INFO_STREAM("Thread["<< boost::this_thread::get_id()
    <<"],A 24 hours weather:"<< msg->weather.c_str()); 
}

//回调函数,注意参数是const类型的boost::shared_ptr指针
void airQualityCallback(const my_msg::air_quality_pubConstPtr& msg)
{
  ROS_INFO_STREAM("Thread["<< boost::this_thread::get_id()
    <<"],24 hours air quality:"<< msg->air_quality_index); 
}

int main(int argc, char **argv){
  ros::init(argc, argv, "multi_subscriber");
  ros::NodeHandle n;
  /*通知ROS master,本node要订阅名为“Weather”的话题(topic),
  并指定回调函数weatherCallback*/
  ros::Subscriber sub = n.subscribe("Weather", 48, weatherCallback);
  ros::Subscriber sub_a = n.subscribe("WeatherA", 48, weatherCallback_A);

  //需要单独声明一个ros::NodeHandle
  ros::NodeHandle n_1;
  //为这个ros::Nodehandle指定单独的Callback队列
  ros::CallbackQueue my_queue;
  n_1.setCallbackQueue(&my_queue);
  /*通知ROS master,本node要订阅名为“AirQuality”的话题(topic),
  并指定回调函数airQualityCallback*/
  ros::Subscriber air_sub = n_1.subscribe("AirQuality", 48, airQualityCallback);
  
  ROS_INFO_STREAM("Thread["<< boost::this_thread::get_id()<<"]This is main thread.");
  
  //启动两个线程处理全局Callback队列 
  ros::AsyncSpinner spinner(2);
  spinner.start();

  //启动一个线程处理AirQuality单独的队列
  ros::AsyncSpinner spinner_1(1, &my_queue);
  spinner_1.start();
  
  ros::waitForShutdown(); 
}



从执行结果中可以看到,进程中包括四个线程:主线程、全局队列Spinner线程1、全局队列Spinner线程2,以及本地队列Spinner线程3。尽管Spinner线程1被回调函数中的死循环卡住,但并不影响其他topic的回调处理。


[ INFO] [1637132247.535142399]: Thread[7f73e4384780]This is main thread.
[ INFO] [1637132247.743935399]: Thread[7f73d77fe700],A 24 hours weather:Sunny 3926
[ INFO] [1637132247.744032493]: Thread[7f73d6ffd700],before loop 24 hours weather:Sunny 3906
[ INFO] [1637132247.744203496]: Thread[7f73d67fc700],24 hours air quality:4034
[ INFO] [1637132247.888403207]: Thread[7f73d77fe700],A 24 hours weather:Sunny 3927
[ INFO] [1637132247.888433359]: Thread[7f73d67fc700],24 hours air quality:4035
[ INFO] [1637132248.088418911]: Thread[7f73d67fc700],24 hours air quality:4036
[ INFO] [1637132248.088461907]: Thread[7f73d77fe700],A 24 hours weather:Sunny 3928
[ INFO] [1637132248.288417795]: Thread[7f73d67fc700],24 hours air quality:4037
[ INFO] [1637132248.288448289]: Thread[7f73d77fe700],A 24 hours weather:Sunny 3929



下图展示了相关的线程和队列处理过程:

在这里插入图片描述

4、使用 ros::AsyncSpinner, 多线程处理回调示例


#include "geometry_msgs/Twist.h"
#include "nav_msgs/Odometry.h"
#include "ros/callback_queue.h"
#include "ros/ros.h"
#include "sensor_msgs/Imu.h"
#include "sensor_msgs/LaserScan.h"
#include "std_msgs/UInt16MultiArray.h"
#include <thread>

class RobotDevice
{
public:
    RobotDevice()
    {
        pub_actuator_ = nh_.advertise<geometry_msgs::Twist>(rostopic_set_actuator_.c_str(), 1);
        thread_ = new std::thread(&RobotDevice::Run, this);
    }

    ~RobotDevice()
    {
        ros::shutdown();
        if (thread_)
        {
            thread_->join();
            delete thread_;
            thread_ = nullptr;
        }
    }

    void PubCmdVelManual(const float data[8])
    {
        memset(&vel_msg_, 0, sizeof(vel_msg_));
        vel_msg_.linear.x = data[0];
        vel_msg_.angular.z = data[1];
        DBUG("SetCtrl linear.x %0.3f, angualr.z %0.3f", data[0], data[1]);
        pub_actuator_.publish(vel_msg_);
    }

private:
    geometry_msgs::Twist vel_msg_;
    ros::NodeHandle nh_;
    ros::Subscriber sub_imu_;
    ros::Subscriber sub_laser_;
    ros::Subscriber sub_actuator_;
    ros::Subscriber sub_sonar_;
    ros::Publisher pub_actuator_;
    ros::CallbackQueue queue_;
    std::thread *thread_ = nullptr;
    const uint32_t ranger_finder_num_ = 7;
    const std::string rostopic_get_imu_ = "/imu";
    const std::string rostopic_get_sonar_ = "/sonar";
    const std::string rostopic_get_laser_ = "/scan_head";
    const std::string rostopic_get_actuator_ = "/odom";
    const std::string rostopic_set_actuator_ = "/cmd_vel_manual";

    void Run()
    {
        nh_.setCallbackQueue(&queue_);
        sub_imu_ = nh_.subscribe(rostopic_get_imu_.c_str(), 10, &RobotDevice::ImuSubCallback, this);
        sub_actuator_ = nh_.subscribe(rostopic_get_actuator_.c_str(), 10, &RobotDevice::ActuatorSubCallback, this);
        sub_laser_ = nh_.subscribe(rostopic_get_laser_.c_str(), 10, &RobotDevice::LaserSubCallback, this);
        sub_sonar_ = nh_.subscribe(rostopic_get_sonar_.c_str(), 10, &RobotDevice::SonarSubCallback, this);
        ros::AsyncSpinner spinner(0, &queue_);
        spinner.start();
        ros::waitForShutdown();
    }

    void ImuSubCallback(const sensor_msgs::Imu::ConstPtr &msg)
    {
        // DBUG("%s %d", __FUNCTION__, __LINE__);
    }

    void ActuatorSubCallback(const nav_msgs::Odometry::ConstPtr &msg)
    {
        // DBUG("%s %d", __FUNCTION__, __LINE__);
    }

    void LaserSubCallback(const sensor_msgs::LaserScan::ConstPtr &msg)
    {
        // DBUG("%s %d", __FUNCTION__, __LINE__);
    }

    void SonarSubCallback(const std_msgs::UInt16MultiArray::ConstPtr &msg)
    {
        // DBUG("%s %d", __FUNCTION__, __LINE__);
    }
};

int main(int argc, char *argv[])
{
	ros::init(argc, argv, "robot_device");
	RobotDevice device;
	ros::Rate rate(10);
	while (ros::ok())
	{
		rate.sleep();
	}
	return 0;
}



本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2188500.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

风场可视化效果的实现,免费的预测数据获得方法

风场可视化是气象学、海洋学等领域中的重要研究工具&#xff0c;它能够直观地展示大气或海洋中的风速、风向等信息。通过风场的可视化&#xff0c;科研人员可以更好地理解气象数据的空间分布特征&#xff0c;分析风场的动力学特性。本文将介绍如何利用Python中的matplotlib、Ba…

git维护【.gitignore文件】

在工程下添加 .gitignore 文件【git忽略文件】 *.class .idea *.iml *.jar /*/target/

如何通过几个简单步骤创建博客

搭建博客不仅可以表达自我和分享知识&#xff0c;还可以成为一种潜在的收入来源。如果你也对搭建博客感兴趣&#xff0c;下面的几个步骤将帮助你轻松入门。 一、选择一个主题 确定你的兴趣点&#xff1a;首先&#xff0c;你需要选择一个你感兴趣且擅长的领域。你悉的领域既能激…

基于SpringBoot+Vue的蛋糕甜品商城系统

系统展示 用户前台界面 管理员后台界面 系统背景 随着互联网技术的飞速发展&#xff0c;电子商务已经深入人们的日常生活&#xff0c;各行各业都在积极拥抱数字化转型。蛋糕甜品行业也不例外&#xff0c;传统的销售模式已经无法满足消费者日益增长的多样化、便捷化需求。因此&a…

每日学习一个数据结构-树

文章目录 树的相关概念一、树的定义二、树的基本术语三、树的分类四、特殊类型的树五、树的遍历六、树的应用场景 树的遍历一、前序遍历二、中序遍历三、后序遍历使用java代码实现遍历总结 树的相关概念 树是一种重要的非线性数据结构&#xff0c;在计算机科学中有着广泛的应用…

Pikachu-File Inclusion-远程文件包含

远程文件包含漏洞 是指能够包含远程服务器上的文件并执行。由于远程服务器的文件是我们可控的&#xff0c;因此漏洞一旦存在&#xff0c;危害性会很大。但远程文件包含漏洞的利用条件较为苛刻&#xff1b;因此&#xff0c;在web应用系统的功能设计上尽量不要让前端用户直接传变…

【GT240X】【04】你必须知道的 50 多个 Linux 命令

文章目录 一、介绍二、五十个linux命令一览表三、50个命令详解四、结论 你必须知道的 50 多个 Linux 命令 一、介绍 你经常使用 Linux 命令&#xff1f;今天&#xff0c;我们将介绍 50 多个你必须知道的 Linux 命令。下面列出的命令是一些最有用和最常用的 Linux 命令&#x…

jmeter学习(5)定时

Jmeter之定时器_jmeter定时器-CSDN博客 Jmeter(十三) - 从入门到精通 - JMeter定时器 - 上篇&#xff08;详解教程&#xff09;-腾讯云开发者社区-腾讯云 (tencent.com) 定时器是在每个sampler之前执行的&#xff0c;无论定时器位置在sampler之前还是子节点下面当执行一个sam…

TypeScript 算法手册 【基数排序】

文章目录 1. 基数排序简介1.1 基数排序定义1.2 基数排序特点 2. 基数排序步骤过程拆解2.1 找出数组中的最大值2.2 从最低位开始&#xff0c;对每一位进行计数排序2.3 对某一位数进行计数排序2.4 将排序结果复制回原数组 3. 基数排序的优化3.1 处理负数3.2 字符串排序案例代码和…

Go语言实现随机森林 (Random Forest)算法

在 Go 语言中实现随机森林&#xff08;Random Forest&#xff09;算法通常涉及以下几个步骤&#xff1a; 数据准备&#xff1a;将数据集分为训练集和测试集&#xff0c;确保数据格式适合算法使用。 决策树的构建&#xff1a;随机森林是由多个决策树构成的&#xff0c;首先需要…

MySQL 实验1:Windows 环境下 MySQL5.5 安装与配置

MySQL 实验1&#xff1a;Windows 环境下 MySQL5.5 安装与配置 目录 MySQL 实验1&#xff1a;Windows 环境下 MySQL5.5 安装与配置一、MySQL 软件的下载二、安装 MySQL三、配置 MySQL1、配置环境变量2、安装并启动 MySQL 服务3、设置 MySQL 字符集4、为 root 用户设置登录密码 一…

使用前端三剑客实现一个备忘录

一&#xff0c;界面介绍 这个备忘录的界面效果如下&#xff1a; 可以实现任务的增删&#xff0c;并且在任务被勾选后会被放到已完成的下面。 示例&#xff1a; &#xff08;1&#xff09;&#xff0c;增加一个任务 &#xff08;2&#xff09;&#xff0c;勾选任务 &#xff…

【知乎直答】批量多线程生成原创文章软件-AI智能搜索聚合

【知乎直答】批量多线程生成原创文章软件介绍&#xff1a; 1、知乎发布的全新AI产品“知乎直答”是其AI搜索功能的产品化成果&#xff0c;旨在提升用户的提问、搜索体验以及结果生成和归纳的质量。 2、数据基础&#xff1a;该产品基于知乎平台上的真实问答数据及全网高质量问答…

Chromium 中前端js XMLHttpRequest接口c++代码实现

在JavaScript中发出HTTP请求的主要方式包括&#xff1a;XMLHttpRequest对象、Fetch API、Axios库和各种其他的HTTP客户端库。 本人主要分析下XMLHttpRequest接口在c中对应实现 一、上前端代码 <!DOCTYPE html> <html lang"en"> <head> <meta…

Go基础学习11-测试工具gomock和monkey的使用

文章目录 基础回顾MockMock是什么安装gomockMock使用1. 创建user.go源文件2. 使用mockgen生成对应的Mock文件3. 使用mockgen命令生成后在对应包mock下可以查看生成的mock文件4. 编写测试代码5. 运行代码并查看输出 GomonkeyGomonkey优势安装使用对函数进行monkey对结构体中方法…

Marp精华总结(二)进阶篇

概述 这是Marp精华总结的第二篇&#xff0c;主要补充第一篇未提到的一些内容。 系列目录 Marp精华总结&#xff08;一&#xff09;基础篇Marp精华总结&#xff08;二&#xff09;进阶篇Marp精华总结&#xff08;三&#xff09;高级篇 自适应标题 通过在标题行中插入<!-…

历经十年/头发都快掉光/秘钥生成器终极版/机器码/到期功能限制/运行时间限制/日期防篡改/跨平台

一、项目介绍 1.0 前言说明 标题一点都不夸张&#xff0c;从第一版的秘钥生成器到今天这个版本&#xff0c;确实经历了十年的时间&#xff0c;最初的版本做的非常简陋&#xff0c;就是搞了个异或加密&#xff0c;控制运行时间&#xff0c;后面又增加设备数量的控制&#xff0…

JavaFX加载fxml文件几种方法

环境&#xff1a;idea&#xff0c;maven创建JavaFX工程 工程目录如下&#xff1a; MusicPlayer.java package cn.com;import java.io.IOException;import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.geometry.Insets; import javafx.geo…

目标检测 Deformable DETR(2021)详细解读

文章目录 前言整体网络架构可变形注意力模块backbone生成多尺度特征多尺度位置编码prediction heads两个变体 前言 为解决DETR attention的计算量大导致收敛速度慢、小目标检测效果差的问题&#xff1a;提出了Deformable Attention&#xff0c;其注意力模块只关注一个query周围…