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, ¬ifies)) {
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, ¬ifies)) {
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是相对的,逻辑是差不多的感兴趣的可以自己去分析下。