CyberRT代码详解(Data)

目录

Data

 DataFusion类

ChannelBuffer类

DataDispatcher类

DataVisitor类

总结


Data

Data目录下有如下类:

DataFusion,AllLatest,ChannelBuffer ,CacheBuffer ,DataDispatcher,DataVisitor,VisitorConfig,DataNotifier等 。

主要功能包括

  • 各个msg的缓冲区,由ChannelBuffer组合CacheBuffer 实现。

  • 消息融合,以及融合消息的缓存,DataFusion

  • 消息分发,Reader侧接收到Writer侧的消息后,由DataDispatcher进行消息存储至指定buffer。DataDispatcher同时会唤醒Processor线程,走协程调度器的逻辑。

  • VisitorConfig用于用户自定义配置,主要包括ringBuffer大小以及channel_id

  • 消息融合策略的定义AllLatest,多路消息下设定msg0为主消息,由msg0根据条件是否满足触发多路消息融合

 

DataFusion类

// DataFusion基类,提供fusion融合接口
template <typename M0, typename M1 = NullType, typename M2 = NullType,
          typename M3 = NullType>
class DataFusion {
 public:
  virtual ~DataFusion() {}
  virtual bool Fusion(uint64_t* index, std::shared_ptr<M0>& m0,  // NOLINT
                      std::shared_ptr<M1>& m1,                   // NOLINT
                      std::shared_ptr<M2>& m2,                   // NOLINT
                      std::shared_ptr<M3>& m3) = 0;              // NOLINT
};
// 实现DataFusion的具体Fusion方法
template <typename M0, typename M1, typename M2>
class AllLatest<M0, M1, M2, NullType> : public DataFusion<M0, M1, M2> {
  using FusionDataType =
      std::tuple<std::shared_ptr<M0>, std::shared_ptr<M1>, std::shared_ptr<M2>>;

 public:
  AllLatest(const ChannelBuffer<M0>& buffer_0,
            const ChannelBuffer<M1>& buffer_1,
            const ChannelBuffer<M2>& buffer_2)
      : buffer_m0_(buffer_0),
        buffer_m1_(buffer_1),
        buffer_m2_(buffer_2),
        buffer_fusion_(buffer_m0_.channel_id(),
                       new CacheBuffer<std::shared_ptr<FusionDataType>>(
                           buffer_0.Buffer()->Capacity() - uint64_t(1))) {
    // 在构造函数中设置fusion_callback,该callback在buffer_m0_传入消息后触发
    // 该回调将对多路消息进行校验,若能够获取多路消息的back,则将各路数据最新值构造成tuple
    // 将消息放置到buffer_fusion_的缓冲区
    buffer_m0_.Buffer()->SetFusionCallback(
        [this](const std::shared_ptr<M0>& m0) {
          std::shared_ptr<M1> m1;
          std::shared_ptr<M2> m2;
          if (!buffer_m1_.Latest(m1) || !buffer_m2_.Latest(m2)) {
            return;
          }

          auto data = std::make_shared<FusionDataType>(m0, m1, m2);
          std::lock_guard<std::mutex> lg(buffer_fusion_.Buffer()->Mutex());
          buffer_fusion_.Buffer()->Fill(data);
        });
  }
  // Fusion函数用于获取buffer_fusion_里对应index的缓存消息,由协程执行函数tryFetch调用
  bool Fusion(uint64_t* index, std::shared_ptr<M0>& m0, std::shared_ptr<M1>& m1,
              std::shared_ptr<M2>& m2) override {
    std::shared_ptr<FusionDataType> fusion_data;
    // 用于获取消息
    if (!buffer_fusion_.Fetch(index, fusion_data)) {
      return false;
    }
    m0 = std::get<0>(*fusion_data);
    m1 = std::get<1>(*fusion_data);
    m2 = std::get<2>(*fusion_data);
    return true;
  }

 private:
  // 多路消息缓冲区
  ChannelBuffer<M0> buffer_m0_;
  ChannelBuffer<M1> buffer_m1_;
  ChannelBuffer<M2> buffer_m2_;
  // 存放由各路消息均到达,满足触发条件的消息所组成消息缓冲区
  ChannelBuffer<FusionDataType> buffer_fusion_;
};

ChannelBuffer类

// 消息缓冲区,组合CacheBuffer对象
template <typename T>
class ChannelBuffer {
 public:
  using BufferType = CacheBuffer<std::shared_ptr<T>>;
  ChannelBuffer(uint64_t channel_id, BufferType* buffer)
      : channel_id_(channel_id), buffer_(buffer) {}
  // 根据索引获取对应的最新的那条消息
  bool Fetch(uint64_t* index, std::shared_ptr<T>& m);  // NOLINT
  // 最新消息
  bool Latest(std::shared_ptr<T>& m);  // NOLINT
  // 获取消息至vec中,可指定消息个数
  bool FetchMulti(uint64_t fetch_size, std::vector<std::shared_ptr<T>>* vec);

  uint64_t channel_id() const { return channel_id_; }
  std::shared_ptr<BufferType> Buffer() const { return buffer_; }

 private:
  uint64_t channel_id_;
  std::shared_ptr<BufferType> buffer_;
};

// ringbuffer结构,是存储消息的队列。提供添加以及获取消息的方法。
// 容量最大值可由外部配置
template <typename T>
class CacheBuffer {
}

DataDispatcher类

DataDispatcher主要用于消息的分发,收到来自pub端消息后,由GetReceiver所封装的Receiver回调触发执行。分发也就是按照channel_id将消息放入对应的缓冲区内。

template <typename MessageT>
auto ReceiverManager<MessageT>::GetReceiver(
    const proto::RoleAttributes& role_attr) ->
    typename std::shared_ptr<transport::Receiver<MessageT>> {
  std::lock_guard<std::mutex> lock(receiver_map_mutex_);
  // because multi reader for one channel will write datacache multi times,
  // so reader for datacache we use map to keep one instance for per channel
  const std::string& channel_name = role_attr.channel_name();
  if (receiver_map_.count(channel_name) == 0) {
    receiver_map_[channel_name] =
        transport::Transport::Instance()->CreateReceiver<MessageT>(
            role_attr, [](const std::shared_ptr<MessageT>& msg,
                          const transport::MessageInfo& msg_info,
                          const proto::RoleAttributes& reader_attr) {
              (void)msg_info;
              (void)reader_attr;
              PerfEventCache::Instance()->AddTransportEvent(
                  TransPerf::DISPATCH, reader_attr.channel_id(),
                  msg_info.seq_num());
              // 触发消息的Dispatch
              data::DataDispatcher<MessageT>::Instance()->Dispatch(
                  reader_attr.channel_id(), msg);
              PerfEventCache::Instance()->AddTransportEvent(
                  TransPerf::NOTIFY, reader_attr.channel_id(),
                  msg_info.seq_num());
            });
  }
  return receiver_map_[channel_name];
}

 由于DataDispatcher是单例模板类,表示每一个类型的消息将对应一个DataDispatcher。

template <typename T>
class DataDispatcher {
 public:
  using BufferVector =
      std::vector<std::weak_ptr<CacheBuffer<std::shared_ptr<T>>>>;
  ~DataDispatcher() {}
  // 添加缓冲区
  void AddBuffer(const ChannelBuffer<T>& channel_buffer);
  // 分发消息至对应的buffer
  bool Dispatch(const uint64_t channel_id, const std::shared_ptr<T>& msg);

 private:
  DataNotifier* notifier_ = DataNotifier::Instance();
  std::mutex buffers_map_mutex_;
  // 存储channel_id和buffer的映射关系
  AtomicHashMap<uint64_t, BufferVector> buffers_map_;
  // 单例声明
  DECLARE_SINGLETON(DataDispatcher)
};

template <typename T>
inline DataDispatcher<T>::DataDispatcher() {}

template <typename T>
void DataDispatcher<T>::AddBuffer(const ChannelBuffer<T>& channel_buffer) {
  std::lock_guard<std::mutex> lock(buffers_map_mutex_);
  auto buffer = channel_buffer.Buffer();
  BufferVector* buffers = nullptr;
  // 添加channel_buffer至buffers_map_
  // 创建DataVisitor对象时调用,将多个相同channel_id而属于不同DataVisitor的存入vector
  if (buffers_map_.Get(channel_buffer.channel_id(), &buffers)) {
    buffers->emplace_back(buffer);
  } else {
    BufferVector new_buffers = {buffer};
    buffers_map_.Set(channel_buffer.channel_id(), new_buffers);
  }
}

template <typename T>
bool DataDispatcher<T>::Dispatch(const uint64_t channel_id,
                                 const std::shared_ptr<T>& msg) {
  BufferVector* buffers = nullptr;
  if (apollo::cyber::IsShutdown()) {
    return false;
  }
  if (buffers_map_.Get(channel_id, &buffers)) {
    // 基于channel_id获取buffers,并添加对应msg至buffer
    for (auto& buffer_wptr : *buffers) {
      if (auto buffer = buffer_wptr.lock()) {
        std::lock_guard<std::mutex> lock(buffer->Mutex());
        buffer->Fill(msg);
      }
    }
  } else {
    return false;
  }
  return notifier_->Notify(channel_id);
}
// 这里的Notify将调用单例DataNotifier里的Notify。
// 将调用NotifyProcessor函数。会唤醒相关联的协程,后续由Processor调度
class DataNotifier {
 public:
  using NotifyVector = std::vector<std::shared_ptr<Notifier>>;
  ~DataNotifier() {}

  void AddNotifier(uint64_t channel_id,
                   const std::shared_ptr<Notifier>& notifier);

  bool Notify(const uint64_t channel_id);

 private:
  std::mutex notifies_map_mutex_;
  // channel_id<-->NotifyProcessor回调函数
  AtomicHashMap<uint64_t, NotifyVector> notifies_map_;
  // 单例声明
  DECLARE_SINGLETON(DataNotifier)
};

DataVisitor类

// 用于用户设置channel_id和ringbuffer最大容量
struct VisitorConfig {
  VisitorConfig(uint64_t id, uint32_t size)
      : channel_id(id), queue_size(size) {}
  uint64_t channel_id;
  uint32_t queue_size;
};

template <typename M0, typename M1 = NullType, typename M2 = NullType,
          typename M3 = NullType>
class DataVisitor : public DataVisitorBase {
 public:
  explicit DataVisitor(const std::vector<VisitorConfig>& configs)
      : buffer_m0_(configs[0].channel_id,
                   new BufferType<M0>(configs[0].queue_size)),
        buffer_m1_(configs[1].channel_id,
                   new BufferType<M1>(configs[1].queue_size)),
        buffer_m2_(configs[2].channel_id,
                   new BufferType<M2>(configs[2].queue_size)),
        buffer_m3_(configs[3].channel_id,
                   new BufferType<M3>(configs[3].queue_size)) {
    // 构造各路msg的缓冲区
    DataDispatcher<M0>::Instance()->AddBuffer(buffer_m0_);
    DataDispatcher<M1>::Instance()->AddBuffer(buffer_m1_);
    DataDispatcher<M2>::Instance()->AddBuffer(buffer_m2_);
    DataDispatcher<M3>::Instance()->AddBuffer(buffer_m3_);
    // msg0路消息为主要消息,为其添加一个唤醒对象
    // 只有msg0消息来了之后再进行融合等操作
    data_notifier_->AddNotifier(buffer_m0_.channel_id(), notifier_);
    // data_fusion存储的是满足融合条件的all msgs构成的tuple
    data_fusion_ = new fusion::AllLatest<M0, M1, M2, M3>(
        buffer_m0_, buffer_m1_, buffer_m2_, buffer_m3_);
  }

  ~DataVisitor() {
    if (data_fusion_) {
      delete data_fusion_;
      data_fusion_ = nullptr;
    }
  }
  // 该函数由协程调度器调用。用于获取最新的各路融合消息
  bool TryFetch(std::shared_ptr<M0>& m0, std::shared_ptr<M1>& m1,    // NOLINT
                std::shared_ptr<M2>& m2, std::shared_ptr<M3>& m3) {  // NOLINT
    if (data_fusion_->Fusion(&next_msg_index_, m0, m1, m2, m3)) {
      next_msg_index_++;
      return true;
    }
    return false;
  }

 private:
  fusion::DataFusion<M0, M1, M2, M3>* data_fusion_ = nullptr;
  ChannelBuffer<M0> buffer_m0_;
  ChannelBuffer<M1> buffer_m1_;
  ChannelBuffer<M2> buffer_m2_;
  ChannelBuffer<M3> buffer_m3_;
};

总结

  • component在初始化时按照msg个数创建Reader个数,每个Reader对应一个datavistor;为每个Reader创建协程函数,该函数用于将消息放置Block中。

  • component会创建一个具有多个消息的datavisitor,包含多个消息单独的buffer以及融合buffer,其对应的协程函数为尝试获取融合消息,若满足融合条件则执行用户回调Processor。融合事件由msg0触发,满足所有消息均到达的条件后,将所有消息构造成tuple放置在data_fusion的ChannelBuffer内。

  • 普通的msg到达后,由DataDispatcher进行分发,将msg存储至每个datavisitor缓冲区即ChannelBuffer内。

  • 由DataNotify单例进行协程任务的唤醒。当消息到达后进行唤醒。

CyberRT所提供多路消息融合的方式有一定的局限性,融合的消息最多仅支持四路消息,同时融合算法较为单一,提供了FetchMulti方式可用于获取指定个数的消息数目,并且Fusion条件满足都是由msg0触发,用户很难构造更为灵活的“表达式”来进行触发的条件设置以及消息获取方式设置。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值