ROS2探索(五)intra-process的内部原理

本文详细介绍了ROS2中的intra-process机制,通过分析示例代码展示其在同一个进程内不同节点间实现消息零拷贝的原理。实验表明,intra-process可以提高性能,但无法在不同进程间实现零拷贝。实验中通过修改代码观察了不同场景下的消息传递情况,揭示了publisher和subscriber如何通过IntraProcessManager进行交互,以及回调函数如何处理接收到的消息。
摘要由CSDN通过智能技术生成

前言

最近一段时间发生不少事情但是一直没有忘记ROS2。
前面介绍了节点基于DDS进行数据传输,Participant收到CacheChange后会将它复制一份到自己的History中,这些Participant不要求在同一个进程或者主机上。然而对于一些涉及到图片和点云的应用中,往往会将不同的处理步骤解耦成若干个节点,比如激光雷达节点只负责发布点云,滤波节点订阅原始点云然后做预处理再发布出去;摄像头节点负责发布图像数据,显示模块订阅后进行界面显示。
ROS2中提供了intra-process机制使得在同一个进程中的节点间减少消息的拷贝开销,保持模块化同时提高了性能。在ros2 foxy源码中有intra-process的demo示例,本文从这些demo入手分析下其内部的实现原理。实验中使用的测试视频来自网上,不接收律师函…

实验

关于intra-process的demo代码在ros2/demos/intra_process_demo文件夹下,文件结构如下:

:~/ros2_foxy/src/ros2/demos/intra_process_demo$ tree
.
├── CHANGELOG.rst
├── CMakeLists.txt
├── include
│ └── image_pipeline
│ ├── camera_node.hpp
│ ├── common.hpp
│ ├── image_view_node.hpp
│ └── watermark_node.hpp
├── package.xml
├── src
│ ├── cyclic_pipeline
│ │ └── cyclic_pipeline.cpp
│ ├── image_pipeline
│ │ ├── camera_node.cpp
│ │ ├── image_pipeline_all_in_one.cpp
│ │ ├── image_pipeline_with_two_image_view.cpp
│ │ ├── image_view_node.cpp
│ │ └── watermark_node.cpp
│ └── two_node_pipeline
│ └── two_node_pipeline.cpp
└── test

我们主要看一下image_pipeline的部分,它包含三个功能节点:camera_node、watermark_node和image_view_node,分别实现了原始图片的发布,添加水印以及图片展示三个功能。
demo展示了三个使用场景:

  1. 三个节点各自为一个进程
  2. 三个节点都在一个进程中
  3. 一个进程中有一个camera_node、一个watermark_node和两个image_view_node

原始的demo代码camera_node使用OpenCV打开摄像头获取图像,这里我没有摄像头所以祭出了我珍藏的视频,代码改动如下:

/**42行构造函数里修改VideoCapturer打开视频文件**/
    //cap_.open(device);
    cap_.open("/home/wenhui/cxk.mp4");
    
/**loop函数开头添加3行**/
    double rate = cap_.get(cv::CAP_PROP_FPS); 
    int delay = 1000000/rate;
    int cnt = 0;
    
/**while循环里添加显示更多的信息**/
	ss << "CameraNode,pid: " << GETPID() << ", ptr: " << msg.get()<<", frame id: "<<cnt;
    
/**loop函数中while结尾添加sleep控制帧率**/
	usleep(delay);
    cnt++;

修改完毕后进行编译,使用–packages-select参数只编译一个包:

colcon build --packages-select intra_process_demo

编译完毕后起三个终端依次分别执行:

终端1:ros2 run intra_process_demo image_view_node
终端2:ros2 run intra_process_demo watermark_node
终端3:ros2 run intra_process_demo camera_node

当终端3启动后出现视频窗口,按空格键可以暂停画面,可以看到:
在这里插入图片描述

左上角显示三个节点依次打印的信息,他们的pid不一样,各自节点内图片消息的指针ptr指向位置也不一样,可以看出发生了图像拷贝。
关闭三个终端,新起一个终端执行:

ros2 run intra_process_demo image_pipeline_all_in_one

按空格键暂停画面后得到:
在这里插入图片描述
三个节点打印的pid一致,各自的图像指针指向位置也完全一致,没有发生图片的拷贝。
重开一个终端执行:

ros2 run intra_process_demo image_pipeline_with_two_image_view

按空格得到效果:在这里插入图片描述
两个窗口对应两个view_node,两幅图片中区别在于view_node中图像地址不同且frame id也有差别,water-marknode的图像地址与camera_node一致但是两个imageview中的图像地址一个与前面相同但另一个不同,两个viewnode中具体哪一个不同具有随机性。

实验现象总结一下:

  1. intra_process可以实现在同一个进程中不同node间的消息0拷贝
  2. intra_process对于不同进程间的node无法实现0拷贝(废话…那样就是inter_process…)
  3. 如果多个消费者同时需要上一层生产者的消息时,同一个消息只能以0拷贝方式传给其中一个消费者,其他的只能拷贝过去

代码分析

上面的实验使我们对intra_process有了大致的轮廓,下面从实验代码为入口扒一下intra_process的运作过程。

demo代码

首先看下camera_node的构造函数:

  CameraNode(
    const std::string & output, const std::string & node_name = "camera_node",
    bool watermark = true, int device = 0, int width = 320, int height = 240)
  : Node(node_name, rclcpp::NodeOptions().use_intra_process_comms(true)),
    canceled_(false), watermark_(watermark)
    {
    	...
	}

可以看到CameraNode继承自Node且设置了use_intra_process_comms为true,这样可以使用intra_process机制。

Camera_node的核心部分:

   while (rclcpp::ok() && !canceled_.load()) {
      // Capture a frame from OpenCV.
      cap_ >> frame_;
      if (frame_.empty()) {
        continue;
      }
      // Create a new unique_ptr to an Image message for storage.
      sensor_msgs::msg::Image::UniquePtr msg(new sensor_msgs::msg::Image());

      if (watermark_) {
        std::stringstream ss;
        // Put this process's id and the msg's pointer address on the image.
        ss << "CameraNode,pid: " << GETPID() << ", ptr: " << msg.get()<<", frame id: "<<cnt;
        draw_on_image(frame_, ss.str(), 20);
      }
      // Pack the OpenCV image into the ROS image.
      set_now(msg->header.stamp);
      msg->header.frame_id = "camera_frame";
      msg->height = frame_.rows;
      msg->width = frame_.cols;
      msg->encoding = mat_type2encoding(frame_.type());
      msg->is_bigendian = false;
      msg->step = static_cast<sensor_msgs::msg::Image::_step_type>(frame_.step);
      msg->data.assign(frame_.datastart, frame_.dataend);
      pub_->publish(std::move(msg));  // Publish.
      cnt++;
      std::this_thread::sleep_for(std::chrono::milliseconds(delay));
    }

while循环中通过cv::VideoCapture读取视频帧并构造sensor_msgs::msg::Image消息msg发布出去。注意这里的msg是一个智能指针sensor_msgs::msg::Image::UniquePtr,它的原型是STL的std::unique_ptr:

using UniquePtrWithDeleter = std::unique_ptr<sensor_msgs::msg::Image_, Deleter>;
using UniquePtr = UniquePtrWithDeleter<>;

watermark_node的关键代码如下:

  WatermarkNode(
    const std::string & input, const std::string & output, const std::string & text,
    const std::string & node_name = "watermark_node")
  : Node(node_name, rclcpp::NodeOptions().use_intra_process_comms(true))
  {
    rclcpp::SensorDataQoS qos;
    // Create a publisher on the input topic.
    pub_ = this->create_publisher<sensor_msgs::msg::Image>(output, qos);
    std::weak_ptr<std::remove_pointer<decltype(pub_.get())>::type> captured_pub = pub_;
    // Create a subscription on the output topic.
    sub_ = this->create_subscription<sensor_msgs::msg::Image>(
      input,
      qos,
      [captured_pub, text](sensor_msgs::msg::Image::UniquePtr msg) {
        auto pub_ptr = captured_pub.lock();
        if (!pub_ptr) {
          return;
        }
        // Create a cv::Mat from the image message (without copying).
        cv::Mat cv_mat(
          msg->height, msg->width,
          encoding2mat_type(msg->encoding),
          msg->data.data());
        // Annotate the image with the pid, pointer address, and the watermark text.
        std::stringstream ss;
        ss << "WatermarkNode,pid: " << GETPID() << ", ptr: " << msg.get() << " " << text;
        draw_on_image(cv_mat, ss.str(), 40);
        pub_ptr->publish(std::move(msg));  // Publish it along.
      });
  }

watermark_node同样设置了use_intra_process_comms,接着创建了sub_用来订阅上面camera_node发布的图片,订阅的回调函数是一个lambda函数,其绑定了captured_pub和text,回调函数的参数类型为sensor_msgs::msg::Image::UniquePtr,经过draw_on_image处理后的图片UniquePtr通过std::move转成右值并pub出去。
最后看下ImageViewNode:

  explicit ImageViewNode(
    const std::string & input, const std::string & node_name = "image_view_node",
    bool watermark = true)
  : Node(node_name, rclcpp::NodeOptions().use_intra_process_comms(true))
  {
    // Create a subscription on the input topic.
    sub_ = this->create_subscription<sensor_msgs::msg::Image>(
      input,
      rclcpp::SensorDataQoS(),
      [node_name, watermark](const sensor_msgs::msg::Image::SharedPtr msg) {
        // Create a cv::Mat from the image message (without copying).
        cv::Mat cv_mat(
          msg->height, msg->width,
          encoding2mat_type(msg->encoding),
          msg->data.data());
        if (watermark) {
          // Annotate with the pid and pointer address.
          std::stringstream ss;
          ss << "ImageViewNode,pid: " << GETPID() << ", ptr: " << msg.get();
          draw_on_image(cv_mat, ss.str(), 60);
        }
        // Show the image.
        cv::Mat c_mat = cv_mat;
        cv::imshow(node_name.c_str(), c_mat);
        char key = cv::waitKey(1);    // Look for key presses.
        if (key == 27 /* ESC */ || key == 'q') {
          rclcpp::shutdown();
        }
        ...
      });
  }

use_intra_process_comms设置为true,create_subscription创建订阅关系并传入回调lambda函数。
关键的来了: 回调lambda中参数类型是const sensor_msgs::msg::Image::SharedPtr,它和watermark_node的回调参数类型不同。前面看到watermark_node中pub的数据是UniquePtr(通过std::move),而这里被转成了SharedPtr。

到这里Demo中主要的部分了解了,可以看出三个功能node都一开始设置了use_intra_process_comms,然后消息node订阅和发布的msg通过UniquePtr或者SharedPtr进行管理。因此我们先看一下use_intra_process_comms做了什么。

IntraProcessManager引入

我们以watermark_node为例,构造函数中设置实例化了Node基类且传入use_intra_process_comms为true的option,这样就设置NodeBase::use_intra_process_default_成员变量为true;后续可以通过rclcpp::detail::resolve_use_intra_process函数来判断某个node是否启用intra-process。
接下来从publisher和subscriber两个方向观察use_intra_process_default_带来了哪些不同。
ROS2探索(一)Publisher-Subscriber的内部过程里面publisher在创建的过程中调用了post_init_setup函数,当时没有具体介绍其作用,它的实现如下:

virtual void post_init_setup(
    rclcpp::node_interfaces::NodeBaseInterface * node_base,
    const std::string & topic,
    const rclcpp::QoS & qos,
    const rclcpp::PublisherOptionsWithAllocator<AllocatorT> & options)
  {
    // Topic is unused for now.
    (void)topic;
    (void)options;

    // If needed, setup intra process communication.
    if (rclcpp::detail::resolve_use_intra_process(options_, *node_base)) {
      auto context = node_base->get_context();
      // Get the intra process manager instance for this context.
      auto ipm = context->get_sub_context<rclcpp::experimental::IntraProcessManager>();
      // Register the publisher with the intra process manager.
      ...
      uint64_t intra_process_publisher_id = ipm->add_publisher(this->shared_from_this());
      this->setup_intra_process(
        intra_process_publisher_id,
        ipm);
    }
  }

可以看到一个启用intra-process的publisher需要IntraProcessManager这个东西,IntraProcessManager可以通过add_publisher来添加相关的publisher,添加后IntraProcessManager会返回一个uint64_t类型的标识ID;然后通过setup_intra_process将创建好的publisher对象与IntraProcessManager进行关联。
对于subscriber这边,回顾下Subscription的构造函数,摘要如下:

    Subscription(...)
        : SubscriptionBase(
              node_base,
              type_support_handle,
              topic_name,
              options.template to_rcl_subscription_options<CallbackMessageT>(qos),
              rclcpp::subscription_traits::is_serialized_subscription_argument<CallbackMessageT>::value),
          any_callback_(callback),
          options_(options),
          message_memory_strategy_(message_memory_strategy)
    {
    	...
      // Setup intra process publishing if requested.
      if (rclcpp::detail::resolve_use_intra_process(options, *node_base))
      {
        using rclcpp::detail::resolve_intra_process_buffer_type;

        // Check if the QoS is compatible with intra-process.
        ...

        // First create a SubscriptionIntraProcess which will be given to the intra-process manager.
        auto context = node_base->get_context();
        using SubscriptionIntraProcessT = rclcpp::experimental::SubscriptionIntraProcess<
            CallbackMessageT,
            AllocatorT,
            typename MessageUniquePtr::deleter_type>;
        auto subscription_intra_process = std::make_shared<SubscriptionIntraProcessT>(
            callback,
            options.get_allocator(),
            context,
            this->get_topic_name(), // important to get like this, as it has the fully-qualified name
            qos_profile,
            resolve_intra_process_buffer_type(options.intra_process_buffer_type, callback));
            ...
        // Add it to the intra process manager.
        using rclcpp::experimental::IntraProcessManager;
        auto ipm = context->get_sub_context<IntraProcessManager>();
        uint64_t intra_process_subscription_id = ipm->add_subscription(subscription_intra_process);
        this->setup_intra_process(intra_process_subscription_id, ipm);
      }

      ...
    }

构造函数中通过resolve_use_intra_process辅助函数检查是否开启intra-process,如果开启则获取IntraProcessManager然后add_subscription,注意这里使用了SubscriptionIntraProcess类型作为AnyExecutable内容来供Executor执行,SubscriptionIntraProcess与普通的Subscription不同,它继承自rclcpp::Waitable

class SubscriptionIntraProcessBase : public rclcpp::Waitable

最后一步和publisher一样也是使用setup_intra_process关联该subscription与IntraProcessManager。

总结一下,当node被设置为use_intra_process后,该node创建subsciber或者pubscriber时会使用IntraProcessManager,IntraProcessManager初始化的步骤就是:

  1. get,获取IntraProcessManager实例
  2. add,向IntraProcessManager添加publisher或者subscription,注意订阅使用的是waitable类型的SubscriptionIntraProcess
  3. set,使用setup_intra_process将publisher或者SubscriptionIntraProcess与IntraProcessManager关联

IntraProcessManager做了什么

前面引入了IntraProcessManager这个东西,然后看到如果开启了intra-process那么publisher和subscription创建后需要与IntraProcessManager进行关联。接下来分析一下IntraProcessManager在消息传送中具体做了哪些工作。
首先看publish函数,demo代码中使用的是unique_ptr参数,实现如下:

  virtual void
  publish(std::unique_ptr<MessageT, MessageDeleter> msg)
  {
    if (!intra_process_is_enabled_) {
      this->do_inter_process_publish(*msg);
      return;
    }
    //
    bool inter_process_publish_needed =
      get_subscription_count() > get_intra_process_subscription_count();

    if (inter_process_publish_needed) {
      auto shared_msg = this->do_intra_process_publish_and_return_shared(std::move(msg));
      this->do_inter_process_publish(*shared_msg);
    } else {
      this->do_intra_process_publish(std::move(msg));
    }
  }

接着publisher的do_intra_process_publish中调用了IntraProcessManager的do_intra_process_publish接口:

  void
  do_intra_process_publish(std::unique_ptr<MessageT, MessageDeleter> msg)
  {
  	...
    ipm->template do_intra_process_publish<MessageT, AllocatorT>(
      intra_process_publisher_id_,
      std::move(msg),
      message_allocator_);
  }

现在来看IntraProcessManager的do_intra_process_publish实现:

  template<
    typename MessageT,
    typename Alloc = std::allocator<void>,
    typename Deleter = std::default_delete<MessageT>>
  void do_intra_process_publish(
    uint64_t intra_process_publisher_id,
    std::unique_ptr<MessageT, Deleter> message,
    std::shared_ptr<typename allocator::AllocRebind<MessageT, Alloc>::allocator_type> allocator);

首先IntraProcessManager检查publisher是否有对应的subscription:

    auto publisher_it = pub_to_subs_.find(intra_process_publisher_id);
    if (publisher_it == pub_to_subs_.end()) {
      // Publisher is either invalid or no longer exists.
      RCLCPP_WARN(
        rclcpp::get_logger("rclcpp"),
        "Calling do_intra_process_publish for invalid or no longer existing publisher id");
      return;
    }

这里体现了IntraProcessManager的一个职责就是维护订阅和发布关系,主要通过保存三个unordered_map来实现:

 struct SplittedSubscriptions
  {
    std::vector<uint64_t> take_shared_subscriptions;
    std::vector<uint64_t> take_ownership_subscriptions;
  };
  using SubscriptionMap =
    std::unordered_map<uint64_t, SubscriptionInfo>;

  using PublisherMap =
    std::unordered_map<uint64_t, PublisherInfo>;

  using PublisherToSubscriptionIdsMap =
    std::unordered_map<uint64_t, SplittedSubscriptions>;

  PublisherToSubscriptionIdsMap pub_to_subs_;
  SubscriptionMap subscriptions_;
  PublisherMap publishers_;

pub_to_subs_储存了一个publisher被哪些subscription订阅,SplittedSubscriptions包含两个数组,分别表示可共享订阅和独占订阅。每个publisher和subscription由IntraProcessManager分配一个uint64_t唯一标识。

接下来,IntraProcessManager将publisher发布的消息分别传送到SubscriptionIntraProcess的buffer_成员中。上面看到subscription由共享和独占两种,IntraProcessManager做了三种处理方法:

  1. publisher所有subscription都是共享的。直接将消息从unique_ptr提升为shared_ptr,用add_shared_msg_to_buffers分发数据
  2. subscription都是独占的,或者只有一个是共享的。等价于所有subscription都是独占,用add_owned_msg_to_buffers分发数据
  3. 既有独占又有共享且数量都不止一个。先将消息拷贝,分发给共享subscription,然后再分发给独享subscription。

上面演示的demo中所有的subscription都是独占的,我当时感到非常奇怪,明明view_node采用了sharedPtr参数回调呀。。。后来仔细看了才发现在创建subscription时,resolve_intra_process_buffer_type里如果IntraProcessBufferType是CallbackDefault,则根据回调函数的参数类型进行解析。但是AnySubscription里面的use_take_shared_method判断依据是:
return const_shared_ptr_callback_ || const_shared_ptr_with_info_callback_;
但view_node的回调函数参数类型是SharedPtrCallback,不是ConstSharedPtrCallback,也不是ConstSharedPtrWithInfoCallback!
综上,view_node所创建的subscription是独占的(take_ownership)

add_shared_msg_to_buffers的实现如下:

  template<typename MessageT>
  void
  add_shared_msg_to_buffers(
    std::shared_ptr<const MessageT> message,
    std::vector<uint64_t> subscription_ids)
  {
    for (auto id : subscription_ids) {
      auto subscription_it = subscriptions_.find(id);
      if (subscription_it == subscriptions_.end()) {
        throw std::runtime_error("subscription has unexpectedly gone out of scope");
      }
      auto subscription_base = subscription_it->second.subscription;

      auto subscription = std::static_pointer_cast<
        rclcpp::experimental::SubscriptionIntraProcess<MessageT>
        >(subscription_base);

      subscription->provide_intra_process_message(message);
    }
  }

比较简单,就是遍历subscription然后调用subscription的provide_intra_process_message接口。
provide_intra_process_message则将数据传到相应的buffer里,并设置waitable监听的ready条件供executor查询。
add_owned_msg_to_buffers与add_shared_msg_to_buffers类似,不过对于前面n-1个subscription做数据拷贝,最后一个不拷贝:

      if (std::next(it) == subscription_ids.end()) {
        // If this is the last subscription, give up ownership
        subscription->provide_intra_process_message(std::move(message));
      } 
      else {
        // Copy the message since we have additional subscriptions to serve
        MessageUniquePtr copy_message;
        Deleter deleter = message.get_deleter();
        auto ptr = MessageAllocTraits::allocate(*allocator.get(), 1);
        MessageAllocTraits::construct(*allocator.get(), ptr, *message);
        copy_message = MessageUniquePtr(ptr, deleter);
        subscription->provide_intra_process_message(std::move(copy_message));
      }

总结一下,IntraProcessManager做了下面的几件事:

  1. 为需要intra-process通信的publisher和subscription分配标识符
  2. 维护上面publisher和subscription之间的通信映射关系
  3. 根据subscription的回调类型决定如何分发消息,一对一不拷贝,一对多会自动拷贝n-1个

Intra-process回调的执行过程

目光转到subscription这边。ROS2探索(二)executor 中介绍了executor处理订阅的过程,其核心是Executor::execute_any_executable函数,intra-process的订阅是一个SubscriptionIntraProcess(即waitable),每当SubscriptionIntraProcess的buffer被塞入数据,它就会变成ready,exetutor就会调用:

  if (any_exec.waitable)
  {
    any_exec.waitable->execute();
  }

execute使用的是SubscriptionIntraProcess的成员函数:

  void execute()
  {
    execute_impl<CallbackMessageT>();
  }
  
  template<class T>
  typename std::enable_if<!std::is_same<T, rcl_serialized_message_t>::value, void>::type
  execute_impl()
  {
    rmw_message_info_t msg_info;
    msg_info.publisher_gid = {0, {0}};
    msg_info.from_intra_process = true;

    if (any_callback_.use_take_shared_method()) {
      ConstMessageSharedPtr msg = buffer_->consume_shared();
      any_callback_.dispatch_intra_process(msg, msg_info);
    } else {
      MessageUniquePtr msg = buffer_->consume_unique();
      any_callback_.dispatch_intra_process(std::move(msg), msg_info);
    }
  }

execute_impl中由于我们的图片源消息是一个unique_ptr,所以进入了

src/ros2/rclcpp/rclcpp/include/rclcpp/any_subscription_callback.hpp

里的

void dispatch_intra_process(
        MessageUniquePtr message, const rclcpp::MessageInfo &message_info)
    {
      TRACEPOINT(callback_start, (const void *)this, true);
      if (shared_ptr_callback_) //case A
      {
        typename std::shared_ptr<MessageT> shared_message = std::move(message);
        shared_ptr_callback_(shared_message);
      }
      else if (shared_ptr_with_info_callback_)
      {
        typename std::shared_ptr<MessageT> shared_message = std::move(message);
        shared_ptr_with_info_callback_(shared_message, message_info);
      }
      else if (unique_ptr_callback_)//case B
      {
        unique_ptr_callback_(std::move(message));
      }
      else if (unique_ptr_with_info_callback_)
      {
        unique_ptr_with_info_callback_(std::move(message), message_info);
      }
      else if (const_shared_ptr_callback_ || const_shared_ptr_with_info_callback_)
      {
        throw std::runtime_error(
            "unexpected dispatch_intra_process unique message call"
            " with const shared_ptr callback");
      }
      else
      {
        throw std::runtime_error("unexpected message without any callback set");
      }
      TRACEPOINT(callback_end, (const void *)this);
    }

这个函数我们主要关注第一个和第三个分支,即caseA 和caseB。caseA表示回调函数接受的参数是一个shared_ptr,对应的是view_node,因此将源消息通过std::move转成了std::shared_ptr传给用户的回调函数。
第三个分支caseB对应watermark_node,回调函数输入的参数是unique_ptr,直接用move操作转右值传给回调函数执行。这里注意的是AnySubscriptionCallback有6个成员变量保存用户的回调函数,但同时只能有一个被设置:

    SharedPtrCallback shared_ptr_callback_;
    SharedPtrWithInfoCallback shared_ptr_with_info_callback_;
    ConstSharedPtrCallback const_shared_ptr_callback_;
    ConstSharedPtrWithInfoCallback const_shared_ptr_with_info_callback_;
    UniquePtrCallback unique_ptr_callback_;
    UniquePtrWithInfoCallback unique_ptr_with_info_callback_;

设置回调函数采用的set成员函数,其在src/ros2/rclcpp/rclcpp/include/rclcpp/subscription_factory.hpp中创建subscription中被调用:

any_subscription_callback.set(std::forward(callback));

std::forward是一个“万能转换”,目标类型是CallbackT,而set提供了6种条件模板供编译时进行检查:

    template <
        typename CallbackT,
        typename std::enable_if<
            rclcpp::function_traits::same_arguments<
                CallbackT,
                SharedPtrCallback>::value>::type * = nullptr>
    void set(CallbackT callback)
    {
      shared_ptr_callback_ = callback;
    }

    template <
        typename CallbackT,
        typename std::enable_if<
            rclcpp::function_traits::same_arguments<
                CallbackT,
                SharedPtrWithInfoCallback>::value>::type * = nullptr>
    void set(CallbackT callback)
    {
      shared_ptr_with_info_callback_ = callback;
    }

    template <
        typename CallbackT,
        typename std::enable_if<
            rclcpp::function_traits::same_arguments<
                CallbackT,
                ConstSharedPtrCallback>::value>::type * = nullptr>
    void set(CallbackT callback)
    {
      const_shared_ptr_callback_ = callback;
    }

    template <
        typename CallbackT,
        typename std::enable_if<
            rclcpp::function_traits::same_arguments<
                CallbackT,
                ConstSharedPtrWithInfoCallback>::value>::type * = nullptr>
    void set(CallbackT callback)
    {
      const_shared_ptr_with_info_callback_ = callback;
    }

    template <
        typename CallbackT,
        typename std::enable_if<
            rclcpp::function_traits::same_arguments<
                CallbackT,
                UniquePtrCallback>::value>::type * = nullptr>
    void set(CallbackT callback)
    {
      unique_ptr_callback_ = callback;
    }

    template <
        typename CallbackT,
        typename std::enable_if<
            rclcpp::function_traits::same_arguments<
                CallbackT,
                UniquePtrWithInfoCallback>::value>::type * = nullptr>
    void set(CallbackT callback)
    {
      unique_ptr_with_info_callback_ = callback;
    }

结语

Intra-process机制主要通过IntraProcessManager实现,需要将publisher和subscription注册到IntraProcessManager中并开启node的intra-process属性。IntraProcessManager维护了需要intra-process的publisher与subscription的对应关系,使用unique_ptr进行数据转移避免拷贝操作;而当多个subscription需要同一个publisher数据时,IntraProcessManager虽然会有拷贝但会保持最后一个subscription不拷贝。executor将需要intra-process的subscription看做waitable对象,执行与前面文章中不同的操作,这点需要注意。
最后明确一下,在Foxy版本中,IntraProcessManager连接了Publisher和Subscription,IntraProcessManager负责分发数据并通知该Subscription为ready,不再走RWM层,https://design.ros2.org/articles/intraprocess_communications.html这篇文章中开头的图片在Foxy里面已经不符啦~

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

灰灰h

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值