apollo事件通信機制

        我目前用的是基於ros版本的apollo,在apollo內部有兩種消息傳輸機制,一種是基於ros的回調函數的訂閱<詳細介紹>來實現各個模塊之間的數據通信,還有一種就是事件的通信機制,主要用於感知模塊內部的通信,這種通信是基於進程間的內存共享來實現消息的傳遞.下面我主要介紹第二種消息傳遞的方式:

  • 先看一下事件的實際應用

發佈事件:

void AsyncFusionSubnode::PublishDataAndEvent(
    const double timestamp, const std::string &device_id,
    const SharedDataPtr<FusionItem> &data) {
  CommonSharedDataKey key(timestamp, device_id);
  bool fusion_succ = fusion_data_->Add(key, data);
  if (!fusion_succ) {
    AERROR << "fusion shared data addkey failure";
  }

  ADEBUG << "adding key in fusion shared data " << key.ToString();

  for (size_t idx = 0; idx < pub_meta_events_.size(); ++idx) {
    const EventMeta &event_meta = pub_meta_events_[idx];
    Event event;
    event.event_id = event_meta.event_id;
    event.timestamp = timestamp;
    event.reserve = device_id;
    event_manager_->Publish(event);
  }
}

        事件發佈的函數中要注意兩點:一是fusion_data_->Add(key,data)就是把要傳遞的信息data與key綁定在一起,然後用fusion_data_添加,第二個是event_manager_->Publish(event)把事件添加進隊列.先看一下Event的結構:

struct Event {
  EventID event_id = 0;
  double timestamp = 0.0;
  std::string reserve;
  // TODO(Yangguang Li):
  double local_timestamp = 0.0;  // local timestamp to compute process delay.

  Event() { local_timestamp = TimeUtil::GetCurrentTime(); }

  std::string to_string() const {
    std::ostringstream oss;
    oss << "event_id: " << event_id
        << " timestamp: " << GLOG_TIMESTAMP(timestamp)
        << " reserve: " << reserve;
    return oss.str();
  }
};

        仔細觀察會發現發佈的event並沒有包含任何data的信息,只是實例化了一個event,包含id timespace device_id,那這個data信息是怎麼傳遞出去的呢?再回過頭來看這個與data綁定在一起的key是如何生成的,CommonSharedDataKey key(timestamp, device_id);

struct CommonSharedDataKey {
  CommonSharedDataKey() = default;
  CommonSharedDataKey(const double ts, const std::string &id)
      : timestamp(ts), device_id(id) {}
  virtual std::string ToString() const {
    return device_id +
           (boost::format("%ld") %
            static_cast<int64_t>(timestamp * FLAGS_stamp_enlarge_factor))
               .str();
  }
  double timestamp = 0.0;
  std::string device_id = "";
}

       看到這裏是不是有點明白了,data本身沒有傳遞,只是這個data根據device_id和時間戳生成了一個只屬於這個data的鑰匙key,然後把這個能生成key的device_id和時間戳發佈到事件隊列中,接收的時候先按id接受event,然後獲取事件中的device_id和時間戳來生成key,然後通過key拿到data.(device_id:std::string類型的變量,某種傳感器的name,timespace:時間戳,因爲每一刻的時間戳都不同,所以不同時刻發出去的事件key都是獨一無二的).

  • 我們再繼續深入,考慮一個問題

       一個節點會一直不停的發佈事件,這麼多事件是如何有序的被另一個節點接受然後解析,並且被解析過後的事件是如何處理的?

上文提到過一個東西"事件隊列",

using EventQueue = FixedSizeConQueue<Event>;

       感知內部會每一個event安排一個queue,這個隊列的最大size是5(可以考慮一下爲什麼這個size要設置成5),當然了這個隊列是自己重新改造的符合線程安全的隊列,那麼我們總共有多少個事件的?接觸過apollo的應該更容易理解,apollo有一個config文件,這個文件中每一個edges就是一個事件,感知這個模塊其實就是一個有向無環圖,串聯這些節點的就是這些edges,我們通過讀取config文件來初始化這個各個節點,各個邊,各個sharedData,在初始化edges時,會初始化一個map,如下:

EventQueueMap event_queue_map_;

這個map的key值是時間ID,value值是一個事件隊列,先根據每一個事件ID爲每個事件new一個專屬隊列,隊列最大size是5

      event_queue_map_[event_pb.id()].reset(
          new EventQueue(5));

       事件隊列準備好之後,接下來就是要使用它了.你們也想到了,發佈事件就是按照事件ID往這個隊列push元素,訂閱事件就是從這個隊列pop元素,這樣一來,事件通信機制的基本框架就搭好了

訂閱事件:

bool AsyncFusionSubnode::SubscribeEvents(const EventMeta &event_meta,
                                         std::vector<Event> *events) const {
  Event event;
  // blocking call for each of these events
  while (event_manager_->Subscribe(event_meta.event_id, &event, true)) {
    ADEBUG << "starting subscribing event " << event_meta.event_id;
    // events->push_back(event);
  }

  // only obtain latest event from a sensor queue
  if (event.event_id != 0 && event.timestamp != 0.0) {
    events->push_back(event);
  }
  return true;
}

搞懂了事件之後,要明白就兩個節點之間通過事件通信還要明白另一個東西,sharedData

       看sharedData之前,先理一下總體思路:按照我們之前的分析,A節點需要發佈信息,它不會把這個數據量巨大結構體發給另一個節點,而是通過底層的事件隊列來完成的.明確我們要發出的信息,一個事件ID,一個時間戳,一個Name,發出的同時用時間戳和Name生成一個唯一Key來指向這個數據量巨大的結構體.那麼節點B在接受信息的時候只要能復現這個key,是不是就能拿到這個數據量巨大的結構體了.

  • 明白了總體思路,就來看一下具體的實現:

這個是我們在發佈信息的時候把data跟生成的key綁定在一起,

bool fusion_succ = fusion_data_->Add(key, data);
template <class M>
bool CommonSharedData<M>::Add(const std::string &key,
                              const SharedDataPtr<M> &data) {
  MutexLock lock(&mutex_);
  auto ret = data_map_.emplace(SharedDataPair(key, data));
  if (!ret.second) {
    AWARN << "Duplicate key: " << key;
    return false;
  }

  const uint64_t timestamp = ::time(NULL);
  data_added_time_map_.emplace(DataKeyTimestampPair(key, timestamp));
  ++stat_.add_cnt;
  return true;
}

template <class M>
bool CommonSharedData<M>::Add(const CommonSharedDataKey &key,
                              const SharedDataPtr<M> &data) {
  // update latest_timestamp for SharedData
  latest_timestamp_ = key.timestamp;
  return Add(key.ToString(), data);
}

      其實綁定也很簡單,就是在common_shared_data.cc中維護了一個map(typedef std::unordered_map<std::string, SharedDataPtr<M>> SharedDataMap;),key值是string,value值是一個智能指針,這個指針指向的就是我們數據量巨大的結構體.我們把這個key值,跟指向data結構體的指針,尾插到這個map中.所以我們用一個sharedData類型對象,只要能得到key,是不是就能拿到這個數據量巨大的結構體了.當然這個結構體可以是任何東西,在感知內部,這個結構體往往是某種傳感器一幀的障礙物信息.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章