当前位置:   article > 正文

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

ros2 executors
ROS2机器人编程简述humble-第二章-Parameters .3.4

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

概述

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

使用模板如下:

  1. int main(int argc, char* argv[])
  2. {
  3. // Some initialization.
  4. rclcpp::init(argc, argv);
  5. ...
  6. // Instantiate a node.
  7. rclcpp::Node::SharedPtr node = ...
  8. // Run the executor.
  9. rclcpp::spin(node);
  10. // Shutdown and exit.
  11. ...
  12. return 0;
  13. }
  1. rclcpp::executors::SingleThreadedExecutor executor;
  2. executor.add_node(node);
  3. executor.spin();

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

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

  1. rclcpp::Node::SharedPtr node1 = ...
  2. rclcpp::Node::SharedPtr node2 = ...
  3. rclcpp::Node::SharedPtr node3 = ...
  4. rclcpp::executors::StaticSingleThreadedExecutor executor;
  5. executor.add_node(node1);
  6. executor.add_node(node2);
  7. executor.add_node(node2);
  8. executor.spin();

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

案例

单线程:

  1. #include "rclcpp/rclcpp.hpp"
  2. #include "std_msgs/msg/int32.hpp"
  3. using namespace std::chrono_literals;
  4. using std::placeholders::_1;
  5. class PublisherNode : public rclcpp::Node
  6. {
  7. public:
  8. PublisherNode()
  9. : Node("publisher_node")
  10. {
  11. publisher_ = create_publisher<std_msgs::msg::Int32>("int_topic", 10);
  12. timer_ = create_wall_timer(
  13. 500ms, std::bind(&PublisherNode::timer_callback, this));
  14. }
  15. void timer_callback()
  16. {
  17. message_.data += 1;
  18. publisher_->publish(message_);
  19. }
  20. private:
  21. rclcpp::Publisher<std_msgs::msg::Int32>::SharedPtr publisher_;
  22. rclcpp::TimerBase::SharedPtr timer_;
  23. std_msgs::msg::Int32 message_;
  24. };
  25. class SubscriberNode : public rclcpp::Node
  26. {
  27. public:
  28. SubscriberNode()
  29. : Node("subscriber_node")
  30. {
  31. subscriber_ = create_subscription<std_msgs::msg::Int32>(
  32. "int_topic", 10,
  33. std::bind(&SubscriberNode::callback, this, _1));
  34. }
  35. void callback(const std_msgs::msg::Int32::SharedPtr msg)
  36. {
  37. RCLCPP_INFO(get_logger(), "Hello %d", msg->data);
  38. }
  39. private:
  40. rclcpp::Subscription<std_msgs::msg::Int32>::SharedPtr subscriber_;
  41. };
  42. int main(int argc, char * argv[])
  43. {
  44. rclcpp::init(argc, argv);
  45. auto node_pub = std::make_shared<PublisherNode>();
  46. auto node_sub = std::make_shared<SubscriberNode>();
  47. rclcpp::executors::SingleThreadedExecutor executor;
  48. // rclcpp::executors::MultiThreadedExecutor executor(
  49. // rclcpp::executor::ExecutorArgs(), 8);
  50. executor.add_node(node_pub);
  51. executor.add_node(node_sub);
  52. executor.spin();
  53. rclcpp::shutdown();
  54. return 0;
  55. }

多线程:

  1. #include "rclcpp/rclcpp.hpp"
  2. #include "std_msgs/msg/int32.hpp"
  3. using namespace std::chrono_literals;
  4. using std::placeholders::_1;
  5. class PublisherNode : public rclcpp::Node
  6. {
  7. public:
  8. PublisherNode()
  9. : Node("publisher_node")
  10. {
  11. publisher_ = create_publisher<std_msgs::msg::Int32>("int_topic", 10);
  12. timer_ = create_wall_timer(
  13. 500ms, std::bind(&PublisherNode::timer_callback, this));
  14. }
  15. void timer_callback()
  16. {
  17. message_.data += 1;
  18. publisher_->publish(message_);
  19. }
  20. private:
  21. rclcpp::Publisher<std_msgs::msg::Int32>::SharedPtr publisher_;
  22. rclcpp::TimerBase::SharedPtr timer_;
  23. std_msgs::msg::Int32 message_;
  24. };
  25. class SubscriberNode : public rclcpp::Node
  26. {
  27. public:
  28. SubscriberNode()
  29. : Node("subscriber_node")
  30. {
  31. subscriber_ = create_subscription<std_msgs::msg::Int32>(
  32. "int_topic", 10,
  33. std::bind(&SubscriberNode::callback, this, _1));
  34. }
  35. void callback(const std_msgs::msg::Int32::SharedPtr msg)
  36. {
  37. RCLCPP_INFO(get_logger(), "Hello %d", msg->data);
  38. }
  39. private:
  40. rclcpp::Subscription<std_msgs::msg::Int32>::SharedPtr subscriber_;
  41. };
  42. int main(int argc, char * argv[])
  43. {
  44. rclcpp::init(argc, argv);
  45. auto node_pub = std::make_shared<PublisherNode>();
  46. auto node_sub = std::make_shared<SubscriberNode>();
  47. // rclcpp::executors::SingleThreadedExecutor executor;
  48. rclcpp::executors::MultiThreadedExecutor executor(
  49. rclcpp::executor::ExecutorArgs(), 8);
  50. executor.add_node(node_pub);
  51. executor.add_node(node_sub);
  52. executor.spin();
  53. rclcpp::shutdown();
  54. return 0;
  55. }

调度语义

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

小结

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

  1. 复杂和混合的调度语义。理想情况下,需要定义良好的调度语义来执行正式的时序分析。

  1. 回调可能会发生优先级反转。较高优先级回调可能被较低优先级回调阻止。

  1. 对回调执行顺序没有显式控制。

  1. 对特定主题的触发没有内置控制。

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

部分解决了这些问题:

  • rclcpp WaitSet:rclcpp的WaitSet类允许直接在订阅、计时器、服务服务器、操作服务器等上等待,而不是使用Executor。它可以用于实现确定性的、用户定义的处理序列,可能同时处理来自不同订阅的多个消息。examples_rclp_wait_set包提供了使用此用户级等待集机制的几个示例。

  • rclc执行器:该执行器来自为micro-ROS开发的C客户端库rclc,为用户提供了对回调执行顺序的细粒度控制,并允许自定义触发条件来激活回调。此外,它实现了逻辑执行时间(LET)语义的思想。


声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小舞很执着/article/detail/797762
推荐阅读
相关标签
  

闽ICP备14008679号