【Apollo 6.0】 cyber rt是如何使用Reader读取到Writer发送的数据(顶层逻辑)

Reader是如何读取到Writer的数据(顶层逻辑)

【Apollo 6.0】 cyber rt是如何使用Reader读取到Writer发送的数据(底层逻辑)
【Apollo 6.0】cyber rt是如何进行节点间管理的

​ 对于reader的创建,一般都是由Node进行创建,比如说在Component类当中(Component最多可以同时处理四种消息类型,这里我们就选择一种数据类型来讲解):

reader = node_->CreateReader<M0>(reader_cfg)

​ 因此我们可以分析Node::CreateReader是如何创建出一个Reader的:

template <typename MessageT>
auto Node::CreateReader(const ReaderConfig& config,
                        const CallbackFunc<MessageT>& reader_func)
    -> std::shared_ptr<cyber::Reader<MessageT>> {
  std::lock_guard<std::mutex> lg(readers_mutex_);
  if (readers_.find(config.channel_name) != readers_.end()) {
    AWARN << "Failed to create reader: reader with the same channel already "
             "exists.";
    return nullptr;
  }
  auto reader =
      node_channel_impl_->template CreateReader<MessageT>(config, reader_func);
  if (reader != nullptr) {
    readers_.emplace(std::make_pair(config.channel_name, reader));
  }
  return reader;
}

​ 可以看到Node::CreateReader是调用NodeChannelImpl::CreateReader去创建的,Node::CreateReader有多个函数重载,而NodeChannelImpl::CreateReader也有其对应的重载函数以供使用:

template <typename MessageT>
auto NodeChannelImpl::CreateReader(const ReaderConfig& config,
                                   const CallbackFunc<MessageT>& reader_func)
    -> std::shared_ptr<Reader<MessageT>> {
  //参数的配置,该参数将会被层次的传递下去,可以看到这里只是配置了channel name
  proto::RoleAttributes role_attr;
  role_attr.set_channel_name(config.channel_name);
  role_attr.mutable_qos_profile()->CopyFrom(config.qos_profile);
  return this->template CreateReader<MessageT>(role_attr, reader_func,
                                               config.pending_queue_size);
}

template <typename MessageT>
auto NodeChannelImpl::CreateReader(const proto::RoleAttributes& role_attr,
                                   const CallbackFunc<MessageT>& reader_func,
                                   uint32_t pending_queue_size)
    -> std::shared_ptr<Reader<MessageT>> {
  if (!role_attr.has_channel_name() || role_attr.channel_name().empty()) {
    AERROR << "Can't create a reader with empty channel name!";
    return nullptr;
  }

  proto::RoleAttributes new_attr(role_attr);
  //对attr进行填充
  FillInAttr<MessageT>(&new_attr);

  std::shared_ptr<Reader<MessageT>> reader_ptr = nullptr;
  if (!is_reality_mode_) {
    reader_ptr =
        std::make_shared<blocker::IntraReader<MessageT>>(new_attr, reader_func);
  } else {
    reader_ptr = std::make_shared<Reader<MessageT>>(new_attr, reader_func,
                                                    pending_queue_size);
  }

  RETURN_VAL_IF_NULL(reader_ptr, nullptr);
  
  //调用创建好的reader执行其Init函数
  RETURN_VAL_IF(!reader_ptr->Init(), nullptr);
  return reader_ptr;
}

​ NodeChannelImpl::CreateReader其他的函数重载最终会调用到下面NodeChannelImpl::CreateReader进行Reader的创建,对于is_reality_mode_,我们这边就默认是在真实环境中进行的,因此is_reality_mode_为true。因此我们继续分析Reader:

template <typename MessageT>
Reader<MessageT>::Reader(const proto::RoleAttributes& role_attr,
                         const CallbackFunc<MessageT>& reader_func,
                         uint32_t pending_queue_size)
    : ReaderBase(role_attr),
      pending_queue_size_(pending_queue_size),
      reader_func_(reader_func) {
  blocker_.reset(new blocker::Blocker<MessageT>(blocker::BlockerAttr(
      role_attr.qos_profile().depth(), role_attr.channel_name())));
}

​ 这里的broker是作为Reader的数据管理器,其底层有个queue用来存储接收到的消息,这里先不做分析。Reader的构造函数只是简单的进行初始化工作,回到NodeChannelImpl::CreateReader,可以看到调用了Reader::Init函数:

template <typename MessageT>
bool Reader<MessageT>::Init() {
  if (init_.exchange(true)) {
    return true;
  }
  std::function<void(const std::shared_ptr<MessageT>&)> func;
  //如果创建reader的时候传入的回调函数,那么也会执行该函数,否则只执行Reader::Enqueue,也就是放入blocker中
  if (reader_func_ != nullptr) {
    func = [this](const std::shared_ptr<MessageT>& msg) {
      this->Enqueue(msg);
      this->reader_func_(msg);
    };
  } else {
    func = [this](const std::shared_ptr<MessageT>& msg) { this->Enqueue(msg); };
  }
  auto sched = scheduler::Instance();
  croutine_name_ = role_attr_.node_name() + "_" + role_attr_.channel_name();
  
  //创建DataVisitor数据存储器
  auto dv = std::make_shared<data::DataVisitor<MessageT>>(
      role_attr_.channel_id(), pending_queue_size_);
  // Using factory to wrap templates.
  croutine::RoutineFactory factory =
      croutine::CreateRoutineFactory<MessageT>(std::move(func), dv);
  
  //创建事件处理协程
  if (!sched->CreateTask(factory, croutine_name_)) {
    AERROR << "Create Task Failed!";
    init_.store(false);
    return false;
  }
  
  //获取数据接收器
  receiver_ = ReceiverManager<MessageT>::Instance()->GetReceiver(role_attr_);
  this->role_attr_.set_id(receiver_->id().HashValue());
  channel_manager_ =
      service_discovery::TopologyManager::Instance()->channel_manager();
    
  //加入cyber的网络拓扑当中
  JoinTheTopology();

  return true;
}

​ 我们这里一步步的来分析该函数,DataVisitor其作用就是作为数据的存储器:

template <typename M0, typename M1>
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)) {
    DataDispatcher<M0>::Instance()->AddBuffer(buffer_m0_);
    DataDispatcher<M1>::Instance()->AddBuffer(buffer_m1_);
    data_notifier_->AddNotifier(buffer_m0_.channel_id(), notifier_);
    data_fusion_ = new fusion::AllLatest<M0, M1>(buffer_m0_, buffer_m1_);
  }

template <typename M0, typename M1>
bool TryFetch(std::shared_ptr<M0>& m0, std::shared_ptr<M1>& m1) {  // NOLINT
    if (data_fusion_->Fusion(&next_msg_index_, m0, m1)) {
      next_msg_index_++;
      return true;
    }
    return false;
  }

template <typename M0>
DataVisitor(uint64_t channel_id, uint32_t queue_size)
      : buffer_(channel_id, new BufferType<M0>(queue_size)) {
    DataDispatcher<M0>::Instance()->AddBuffer(buffer_);
    data_notifier_->AddNotifier(buffer_.channel_id(), notifier_);
  }

template <typename M0>
bool TryFetch(std::shared_ptr<M0>& m0) {  // NOLINT
    if (buffer_.Fetch(&next_msg_index_, m0)) {
      next_msg_index_++;
      return true;
    }
    return false;
  }

​ 上面两个DataVisitor的构造函数,一个是处理两个消息类型,一个是处理一个消息类型(也就是我们正在分析的),都创建了消息的Buffer,这些Buffer将会存放接收到的数据。然后可以看到两个构造函数除了消息数量不同之外,唯一区别就是多消息类型的有一个成员变量data_fusion_,那这个data_fusion_是什么东西呢?在多消息类型的DataVisitor当中,由于需要接收多个消息,因此需要对数据做融合,可以看到data_notifier_就添加了buffer_m0_也就是第一个消息的buffer,只有当来了第一个消息之后,才会触发数据的融合机制,从而取出其他几个消息的最后一个消息,然后将几个消息传递出去,因此对于哪个消息作为第一个消息需要认真的思考。可以看到在TryFetch函数当中,TryFetch直接从buffer当中拿出数据,而TryFetch<M0,M1>则是使用了data_fusion_来获取数据,当数据没有融合的时候,则返回false。

​ 说完不同消息数量的DataVisitor的差别,我们继续分析,在构造函数当中使用了DataDispatcher单例来讲创建的buffer进行添加,那么这个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_id已经添加的话,则直接放入BufferVector当中去
  //从这里可以看出来对于同一个channel是可以有多个对应的buffer的
  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);
  }
}

//buffers_map_的定义,AtomicHashMap为cyber实现的lock free的map,感兴趣的可以去了解其实现
AtomicHashMap<uint64_t, BufferVector> buffers_map_

​ 可以看到DataDispatcher就是将DataVisitor所创建的消息buffer放入自己的buffers_map_当中。那么继续分析,下面还有一个data_notifier_,其定义为DataNotifier* data_notifier_ = DataNotifier::Instance(),也是一个单例:

inline void DataNotifier::AddNotifier(
    uint64_t channel_id, const std::shared_ptr<Notifier>& notifier) {
  std::lock_guard<std::mutex> lock(notifies_map_mutex_);
  NotifyVector* notifies = nullptr;
  if (notifies_map_.Get(channel_id, &notifies)) {
    notifies->emplace_back(notifier);
  } else {
    NotifyVector new_notify = {notifier};
    notifies_map_.Set(channel_id, new_notify);
  }
}

//notifies_map_定义
AtomicHashMap<uint64_t, NotifyVector> notifies_map_;

​ DataNotifier::AddNotifier跟DataDispatcher::AddBuffer的功能差不多,不过AddNotifier是添加一个notifier到自己的map当中,那么这个notifier具体的作用是什么呢?

struct Notifier {
  std::function<void()> callback;
};

​ Notifier功能很简单,就是有个callback成员,这个成员将会在消息接收到的时候被调用,后面再做分析。DataVisitor的分析就完了,主要的功能就是将消息的Buffer和Notifier放入到DataDispatcher和DataNotifier当中。

​ 我们继续回到Reader::Init()进行分析:

croutine::RoutineFactory factory =
      croutine::CreateRoutineFactory<MessageT>(std::move(func), dv);
  if (!sched->CreateTask(factory, croutine_name_)) {
    AERROR << "Create Task Failed!";
    init_.store(false);
    return false;
  }

​ 当DataVisitor构造完毕之后,调用了croutine::CreateRoutineFactory创建了一个croutine::RoutineFactory:

template <typename M0, typename F>
RoutineFactory CreateRoutineFactory(
    F&& f, const std::shared_ptr<data::DataVisitor<M0>>& dv) {
  RoutineFactory factory;
  //设置DataVisitor
  factory.SetDataVisitor(dv);
  factory.create_routine = [=]() {
    return [=]() {
      std::shared_ptr<M0> msg;
      for (;;) {
        CRoutine::GetCurrentRoutine()->set_state(RoutineState::DATA_WAIT);
        if (dv->TryFetch(msg)) {
          f(msg);
          CRoutine::Yield(RoutineState::READY);
        } else {
          CRoutine::Yield();
        }
      }
    };
  };
  return factory;
}

​ croutine::CreateRoutineFactory主要就是创建了协程的执行函数,轮询等到消息的到来,然后执行 Reader::Init()中设置的回调函数。

​ 再之后就是调用了sched->CreateTask创建任务:

bool Scheduler::CreateTask(std::function<void()>&& func,
                           const std::string& name,
                           std::shared_ptr<DataVisitorBase> visitor) {
  if (cyber_unlikely(stop_.load())) {
    ADEBUG << "scheduler is stoped, cannot create task!";
    return false;
  }
  
  //主要就是注册这个name,然后返回name的Hash
  auto task_id = GlobalData::RegisterTaskName(name);
  
  //cyber实现的协程,这里不做分析,可以认为是线程就行
  auto cr = std::make_shared<CRoutine>(func);
  cr->set_id(task_id);
  cr->set_name(name);
  AINFO << "create croutine: " << name;
  
  //调度任务
  if (!DispatchTask(cr)) {
    return false;
  }
  
  //visitor为CreateRoutineFactory传入的DataVisitor
  if (visitor != nullptr) {
    visitor->RegisterNotifyCallback([this, task_id]() {
      if (cyber_unlikely(stop_.load())) {
        return;
      }
      this->NotifyProcessor(task_id);
    });
  }
  return true;
}

​ 其中RegisterNotifyCallback为DataVisitor的父类DataVisitorBase实现:

 void RegisterNotifyCallback(std::function<void()>&& callback) {
    notifier_->callback = callback;
  }

​ 可以看到就是设置notifier_的回调,当执行该回调的时候会执行Scheduler::NotifyProcessor来通知Processor来执行协程也就是CreateRoutineFactory里的factory.create_routine。

​ 回到Reader::Init()继续分析:

receiver_ = ReceiverManager<MessageT>::Instance()->GetReceiver(role_attr_);
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是最底层的数据处理类,这里我们不做分析,这里调用其CreateReceiver
        //函数创建一个Receiver,且当收到数据之后,将会调用这里传入的lamda
        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());
              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];
}

​ 可以看到ReceiverManager::GetReceiver的功能就是创建一个receiver然后根据channel_name放入map当中,当底层通信模块接收到数据之后,将会调用到传入的回调函数,在回调函数当中我们看到了上面讲到的data::DataDispatcher,调用了其Dispatch函数,将收到的数据进行分发:

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)) {
    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);
}

//notifier_的定义
DataNotifier* notifier_ = DataNotifier::Instance();

​ 可以看到DataDispatcher::Dispatch使用底层通信模块传入的消息对buffer进行填充,而这里的buffer正是我们在DataVisitor的构造函数中传入的。填充完buffer,那么原先创建的协程就能通过DataVisitor::TryFetch拿出数据,而唤醒协程的就是通过调用notifier_->Notify(channel_id):

inline bool DataNotifier::Notify(const uint64_t channel_id) {
  NotifyVector* notifies = nullptr;
  if (notifies_map_.Get(channel_id, &notifies)) {
    for (auto& notifier : *notifies) {
      if (notifier && notifier->callback) {
        notifier->callback();
      }
    }
    return true;
  }
  return false;
}

​ DataNotifier::Notify从notifies_map_中取出原先在DataVisitor的构造函数中传入的notifier,执行其callback函数,而这个callback是在Scheduler::CreateTask当中设置的,最终调用了Scheduler::NotifyProcessor来唤醒协程。

​ 至此Reader如何从Writer方读取到数据的整个上层数据流向分析完毕,总结一下:

​ Transport —> DataDispatcher —> DataVisitor::buffer —> DataNotifier::Instance()::Notify —> callback —> Scheduler::NotifyProcessor —> 执行协程 —> 从DataVisitor::buffer拿出数据 —> 放入reader的blocker —>执行用户传入的回调(如果有的话)
在这里插入图片描述

​ 下一章我们讲下底层是怎么通信的,而且在Reader::Init()中还有代码没分析:

channel_manager_ =
      service_discovery::TopologyManager::Instance()->channel_manager();
  JoinTheTopology();

​ 这里是cyber的服务发现的功能,也是在后续的章节当中进行分析。
而对于Writer来说与Reader是相对的,逻辑是差不多的感兴趣的可以自己去分析下。

​ 参考链接:
dig-into-apollo
百度Apollo系统学习-Cyber RT 通信-上层

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值