前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ROS2机器人编程简述humble-第二章-Executors .3.5

ROS2机器人编程简述humble-第二章-Executors .3.5

作者头像
zhangrelay
发布2023-02-10 09:26:19
1.3K0
发布2023-02-10 09:26:19
举报
ROS2机器人编程简述humble-第二章-Parameters .3.4

由于ROS2中的节点是C++对象,因此一个进程可以有多个节点。事实上,在许多情况下,这样做是非常有益的,因为当通信处于同一进程中时,可以通过使用共享内存策略来加速通信。另一个好处是,如果节点都在同一个程序中,它可以简化节点的部署。缺点是,一个节点中的故障可能会导致同一进程的所有节点终止。ROS2提供了几种在同一进程中运行多个节点的方法。最推荐的是使用执行器。

概述

ROS 2中的执行管理由执行者的概念来解释。执行器使用底层操作系统的一个或多个线程来调用订阅、计时器、服务服务器、动作服务器等对传入消息和事件的回调。显式Executor类(在rclcpp中的Executor.hpp中,在rclpy中的executions.py中,或在rclc中的executer.h中)提供了比ROS1中的自旋机制更多的执行管理控制,尽管基本API非常相似。

使用模板如下:

代码语言:javascript
复制
int main(int argc, char* argv[])
{
   // Some initialization.
   rclcpp::init(argc, argv);
   ...

   // Instantiate a node.
   rclcpp::Node::SharedPtr node = ...

   // Run the executor.
   rclcpp::spin(node);

   // Shutdown and exit.
   ...
   return 0;
}
代码语言:javascript
复制
rclcpp::executors::SingleThreadedExecutor executor;
executor.add_node(node);
executor.spin();

通过调用Executor实例的spin(),当前线程开始向rcl和中间件层查询传入消息和其他事件,并调用相应的回调函数,直到节点关闭。为了不抵消中间件的QoS设置,传入消息不存储在客户端库层的队列中,而是保留在中间件中,直到回调函数处理该消息。(这是与ROS 1的一个关键区别。)等待集用于通知执行器中间件层上的可用消息,每个队列有一个二进制标志。等待集还用于检测计时器何时过期。

单线程执行器也被容器进程用于组件,即在创建和执行节点时没有显式主函数的所有情况下。

代码语言:javascript
复制
rclcpp::Node::SharedPtr node1 = ...
rclcpp::Node::SharedPtr node2 = ...
rclcpp::Node::SharedPtr node3 = ...

rclcpp::executors::StaticSingleThreadedExecutor executor;
executor.add_node(node1);
executor.add_node(node2);
executor.add_node(node2);
executor.spin();

多线程执行器创建可配置数量的线程,以允许并行处理多个消息或事件。静态单线程执行器优化了在订阅、计时器、服务服务器、动作服务器等方面扫描节点结构的运行时成本。它只在添加节点时执行一次扫描,而其他两个执行器定期扫描此类更改。因此,静态单线程执行器只能用于在初始化期间创建所有订阅、计时器等的节点。

案例

单线程:

代码语言:javascript
复制
#include "rclcpp/rclcpp.hpp"

#include "std_msgs/msg/int32.hpp"

using namespace std::chrono_literals;
using std::placeholders::_1;

class PublisherNode : public rclcpp::Node
{
public:
  PublisherNode()
  : Node("publisher_node")
  {
    publisher_ = create_publisher<std_msgs::msg::Int32>("int_topic", 10);
    timer_ = create_wall_timer(
      500ms, std::bind(&PublisherNode::timer_callback, this));
  }

  void timer_callback()
  {
    message_.data += 1;
    publisher_->publish(message_);
  }

private:
  rclcpp::Publisher<std_msgs::msg::Int32>::SharedPtr publisher_;
  rclcpp::TimerBase::SharedPtr timer_;
  std_msgs::msg::Int32 message_;
};

class SubscriberNode : public rclcpp::Node
{
public:
  SubscriberNode()
  : Node("subscriber_node")
  {
    subscriber_ = create_subscription<std_msgs::msg::Int32>(
      "int_topic", 10,
      std::bind(&SubscriberNode::callback, this, _1));
  }

  void callback(const std_msgs::msg::Int32::SharedPtr msg)
  {
    RCLCPP_INFO(get_logger(), "Hello %d", msg->data);
  }

private:
  rclcpp::Subscription<std_msgs::msg::Int32>::SharedPtr subscriber_;
};

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);

  auto node_pub = std::make_shared<PublisherNode>();
  auto node_sub = std::make_shared<SubscriberNode>();

  rclcpp::executors::SingleThreadedExecutor executor;
  // rclcpp::executors::MultiThreadedExecutor executor(
  //   rclcpp::executor::ExecutorArgs(), 8);

  executor.add_node(node_pub);
  executor.add_node(node_sub);

  executor.spin();

  rclcpp::shutdown();
  return 0;
}

多线程:

代码语言:javascript
复制
#include "rclcpp/rclcpp.hpp"

#include "std_msgs/msg/int32.hpp"

using namespace std::chrono_literals;
using std::placeholders::_1;

class PublisherNode : public rclcpp::Node
{
public:
  PublisherNode()
  : Node("publisher_node")
  {
    publisher_ = create_publisher<std_msgs::msg::Int32>("int_topic", 10);
    timer_ = create_wall_timer(
      500ms, std::bind(&PublisherNode::timer_callback, this));
  }

  void timer_callback()
  {
    message_.data += 1;
    publisher_->publish(message_);
  }

private:
  rclcpp::Publisher<std_msgs::msg::Int32>::SharedPtr publisher_;
  rclcpp::TimerBase::SharedPtr timer_;
  std_msgs::msg::Int32 message_;
};

class SubscriberNode : public rclcpp::Node
{
public:
  SubscriberNode()
  : Node("subscriber_node")
  {
    subscriber_ = create_subscription<std_msgs::msg::Int32>(
      "int_topic", 10,
      std::bind(&SubscriberNode::callback, this, _1));
  }

  void callback(const std_msgs::msg::Int32::SharedPtr msg)
  {
    RCLCPP_INFO(get_logger(), "Hello %d", msg->data);
  }

private:
  rclcpp::Subscription<std_msgs::msg::Int32>::SharedPtr subscriber_;
};

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);

  auto node_pub = std::make_shared<PublisherNode>();
  auto node_sub = std::make_shared<SubscriberNode>();

  // rclcpp::executors::SingleThreadedExecutor executor;
  rclcpp::executors::MultiThreadedExecutor executor(
     rclcpp::executor::ExecutorArgs(), 8);

  executor.add_node(node_pub);
  executor.add_node(node_sub);

  executor.spin();

  rclcpp::shutdown();
  return 0;
}

调度语义

如果回调的处理时间短于消息和事件发生的时间,则执行器基本上按FIFO顺序处理它们。但是,如果某些回调的处理时间较长,消息和事件将在堆栈的较低层排队。等待集机制只向执行器报告关于这些队列的很少信息。详细地说,它只报告是否有关于某个主题的消息。执行器使用此信息以循环方式处理消息(包括服务和操作),但不按FIFO顺序。下面的流程图可视化了这种调度语义。

小结

虽然rclcpp的三个执行器在大多数应用程序中运行良好,但存在一些问题,使它们不适合实时应用程序,因为实时应用程序需要定义良好的执行时间、确定性和对执行顺序的自定义控制。以下是其中一些问题的摘要:

  1. 复杂和混合的调度语义。理想情况下,需要定义良好的调度语义来执行正式的时序分析。
  2. 回调可能会发生优先级反转。较高优先级回调可能被较低优先级回调阻止。
  3. 对回调执行顺序没有显式控制。
  4. 对特定主题的触发没有内置控制。

此外,在CPU和内存使用方面,执行器开销相当大。静态单线程执行器大大减少了这一开销,但对于某些应用程序来说可能还不够。

部分解决了这些问题:

  • rclcpp WaitSet:rclcpp的WaitSet类允许直接在订阅、计时器、服务服务器、操作服务器等上等待,而不是使用Executor。它可以用于实现确定性的、用户定义的处理序列,可能同时处理来自不同订阅的多个消息。examples_rclp_wait_set包提供了使用此用户级等待集机制的几个示例。
  • rclc执行器:该执行器来自为micro-ROS开发的C客户端库rclc,为用户提供了对回调执行顺序的细粒度控制,并允许自定义触发条件来激活回调。此外,它实现了逻辑执行时间(LET)语义的思想。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-01-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • ROS2机器人编程简述humble-第二章-Parameters .3.4
相关产品与服务
消息队列 TDMQ
消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档