WebRTC学习进阶之路 --- 十五、源码分析之WebRTC中的线程详解-MessageQueueManager&MessageQueue&Message&MessageHandler

WebRTC学习进阶之路系列总目录:https://blog.csdn.net/xiaomucgwlmx/article/details/103204274

本文将介绍消息的管理类(MessageQueueManager)、消息循环中的消息(Message)、消息中的数据(MessageData)、消息队列(MessageQueue)以及处理消息的Handler(MessageHandler)的基本内容。

除了MessageHandler均位于rtc_base/message_queue.h和rtc_base/message_queue.cc中,MessageHandler相关的类位于rtc_base/message_handler.h中,下边注意分析。

一、MessageQueueManager

顾名思义,与ThreadManager和Thread的关系类似,MessageQueueManager类是为了对MessageQueue进行管理而存在,源码头文件如下:

class RTC_EXPORT MessageQueueManager {
 public:
  static void Add(MessageQueue* message_queue);
  static void Remove(MessageQueue* message_queue);
  static void Clear(MessageHandler* handler);

  // TODO(nisse): Delete alias, as soon as downstream code is updated.
  static void ProcessAllMessageQueues() { ProcessAllMessageQueuesForTesting(); }

  // For testing purposes, for use with a simulated clock.
  // Ensures that all message queues have processed delayed messages
  // up until the current point in time.
  static void ProcessAllMessageQueuesForTesting();

 private:
  static MessageQueueManager* Instance();

  MessageQueueManager();
  ~MessageQueueManager();

  void AddInternal(MessageQueue* message_queue);
  void RemoveInternal(MessageQueue* message_queue);
  void ClearInternal(MessageHandler* handler);
  void ProcessAllMessageQueuesInternal();

  // This list contains all live MessageQueues.
  std::vector message_queues_ RTC_GUARDED_BY(crit_);

  // Methods that don't modify the list of message queues may be called in a
  // re-entrant fashion. "processing_" keeps track of the depth of re-entrant
  // calls.
  CriticalSection crit_;
  size_t processing_ RTC_GUARDED_BY(crit_);
};

 

同样的MessageQueueManager也采用单例Instance()的方式构造,和上一篇ThreadManager的方式一样,这里不做赘述,下边我们来看下几个核心方法。

1,数据的添加(Add)、删除(Remove)与清空(Clear)

void MessageQueueManager::Add(MessageQueue* message_queue) {
  return Instance()->AddInternal(message_queue);
}
void MessageQueueManager::AddInternal(MessageQueue* message_queue) {
  CritScope cs(&crit_);
  // Prevent changes while the list of message queues is processed.
  RTC_DCHECK_EQ(processing_, 0);
  message_queues_.push_back(message_queue);
}

void MessageQueueManager::Remove(MessageQueue* message_queue) {
  return Instance()->RemoveInternal(message_queue);
}
void MessageQueueManager::RemoveInternal(MessageQueue* message_queue) {
  {
    CritScope cs(&crit_);
    // Prevent changes while the list of message queues is processed.
    RTC_DCHECK_EQ(processing_, 0);
    std::vector::iterator iter;
    iter = absl::c_find(message_queues_, message_queue);
    if (iter != message_queues_.end()) {
      message_queues_.erase(iter);
    }
  }
}

void MessageQueueManager::Clear(MessageHandler* handler) {
  return Instance()->ClearInternal(handler);
}
void MessageQueueManager::ClearInternal(MessageHandler* handler) {
  // Deleted objects may cause re-entrant calls to ClearInternal. This is
  // allowed as the list of message queues does not change while queues are
  // cleared.
  MarkProcessingCritScope cs(&crit_, &processing_);
  for (MessageQueue* queue : message_queues_) {
    queue->Clear(handler);
  }
}
  • crit_:临界区类CriticalSection的对象,该成员保证多线程环境下访问安全,正如上面两个函数所示,函数开头创建CritScope cs(&crit_); 在cs的构造函数中调用crit_->Enter()表示进入临界区,相当于上锁。利用函数结束后cs对象的析构中调用crit_->Leave()表示离开临界区,相当于解锁。
    然后我们要知道CriticalSection是可重入的,也即一个线程上调用cs_->Enter()上锁之后,在释放锁之前,同一个线程可以反复调用cs_->Enter()而不会阻塞,因此被称为“可重入锁”。同一个线程上锁一次,processing_就增1,记录上锁次数,只要processing_不为0,表示我正在Clear操作或者后文的Process*方法,这两个方法不会改变Vector列表,如上代码及注释所示ClearInternal的操作是“删除对象可能导致重新进入对ClearInternal的调用。 这是允许的,因为清除队列时消息队列列表不会更改。”,因此,可以在解锁之前,重入进行反复操作,但是不允许Add和Remove操作,因为其会改变Vector,这就是为什么Add和Remove函数中既加锁了,还要断言processing_必须为0。
  • processing_ :声明为 size_t processing_ RTC_GUARDED_BY(crit_); Add与Remove函数中执行了RTC_DCHECK_EQ(processing_, 0)断言,必须确保processing_ 为0。当processing_不为0时,要么在执行Clear()方法,要么在执行ProcessAllMessageQueues()。

看了这段时间源码,我们发现大部分方法的具体实现都是私有的*Internal*(...)作为内部实现,比较清晰。

2,消息的具体处理

void MessageQueueManager::ProcessAllMessageQueuesInternal() {
  // This works by posting a delayed message at the current time and waiting
  // for it to be dispatched on all queues, which will ensure that all messages
  // that came before it were also dispatched.
  volatile int queues_not_done = 0;

  // This class is used so that whether the posted message is processed, or the
  // message queue is simply cleared, queues_not_done gets decremented.
  class ScopedIncrement : public MessageData {
   public:
    ScopedIncrement(volatile int* value) : value_(value) {
      AtomicOps::Increment(value_);
    }
    ~ScopedIncrement() override { AtomicOps::Decrement(value_); }

   private:
    volatile int* value_;
  };

  {
    MarkProcessingCritScope cs(&crit_, &processing_);
    for (MessageQueue* queue : message_queues_) {
      if (!queue->IsProcessingMessagesForTesting()) {
        // If the queue is not processing messages, it can
        // be ignored. If we tried to post a message to it, it would be dropped
        // or ignored.
        continue;
      }
      queue->PostDelayed(RTC_FROM_HERE, 0, nullptr, MQID_DISPOSE,
                         new ScopedIncrement(&queues_not_done));
    }
  }

  rtc::Thread* current = rtc::Thread::Current();
  // Note: One of the message queues may have been on this thread, which is
  // why we can't synchronously wait for queues_not_done to go to 0; we need
  // to process messages as well.
  while (AtomicOps::AcquireLoad(&queues_not_done) > 0) {
    if (current) {
      current->ProcessMessages(0);
    }
  }
}
  • 线程调用消息处理时会遍历所有的message_queues_,然后向每个MessageQueue中投递一个消息ID为MQID_DISPOSE的延迟消息,MessageData数据为ScopedIncrement对象,ScopedIncrement的构造中调用系统API将queues_not_done原子性自增1(原子性加减https://baike.baidu.com/item/InterLockedIncrement/302345?fr=aladdin),表示该消息队列中有消息没有被处理,而该延迟消息时间为0,那么该延迟消息将进入MessageQueue的延迟消息队列的队首(因为MQ的延迟消息队列是以延迟时间排序的优先级队列)。
  • 获取调用该方法的线程所关联的Thread对象,并通过Thread对象的ProcessMessages方法不断的从当前线程的MQ中取出消息进行处理,直到queues_not_done为0,此时,之前投递到消息循环中的MQID_DISPOSE类别的消息得到处理,因为该消息在消息循环中被取出后,消息数据ScopedIncrement对象会被直接delete,从而ScopedIncrement析构完成queues_not_done原子性自减1。此时,表征着该消息循环中的所有即时消息都得到了处理。消息队列中的一个可能在此线程上,这就是为什么我们无法同步等待queues_not_done变为0的原因; 我们还需要处理消息。

二、Message与DelayedMessage

WebRTC中消息相关的类分为两种,一种是Message即时消息,投放到消息循环中期待能被立马消费;另外一种是DelayedMessage延迟消息,投放到消息循环中不会立马被消费,而是延迟一段时间才会被消费。源码如下:

struct Message {
  Message()
      : phandler(nullptr), message_id(0), pdata(nullptr), ts_sensitive(0) {}
  inline bool Match(MessageHandler* handler, uint32_t id) const {
    return (handler == nullptr || handler == phandler) &&
           (id == MQID_ANY || id == message_id);
  }
  Location posted_from;
  MessageHandler* phandler;
  uint32_t message_id;
  MessageData* pdata;
  int64_t ts_sensitive;
};

typedef std::list MessageList;

// DelayedMessage goes into a priority queue, sorted by trigger time.  Messages
// with the same trigger time are processed in num_ (FIFO) order.
class DelayedMessage {
 public:
  DelayedMessage(int64_t delay,
                 int64_t trigger,
                 uint32_t num,
                 const Message& msg)
      : cmsDelay_(delay), msTrigger_(trigger), num_(num), msg_(msg) {}

  bool operator<(const DelayedMessage& dmsg) const {
    return (dmsg.msTrigger_ < msTrigger_) ||
           ((dmsg.msTrigger_ == msTrigger_) && (dmsg.num_ < num_));
  }

  int64_t cmsDelay_;  // for debugging
  int64_t msTrigger_;
  uint32_t num_;
  Message msg_;
};

1,Message

  • Location posted_from:通过的结构体定义就可以知道Location(const char* function_name, const char* file_and_line);
    posted_from代表了消息的来源,来源于那个方法、哪个文件哪一行;
  • MessageHandler* phandler:主要使用该成员MessageHandler的OnMessage(Message* msg)来对消息进行处理;
  • uint32_t message_id:消息id;
  • MessageData* pdata:消息包含的数据,下边详述;
  • int64_t ts_sensitive:64位时间戳,单位ms。当不关心消息是否处理过慢时,也即消息时间不敏感时,该值为0;若关心消息是否得到即时处理,一般会设置ts_sensitive为消息创建时的时间戳 + kMaxMsgLatency常量(150ms),当该消息从消息循环中取出被处理时,将会检测当前时间msCurrent与ts_sensitive的大小,若msCurrent>ts_sensitive,则表示该消息并没有得到即时的处理,会打印警告日志。超时时间计算为msCurrent-ts_sensitive+kMaxMsgLatency。

2,DelayedMessage

定义了延迟触发消息的数据结构。在 MessageQueue 中,延迟消息被存放在以 * DelayedMessage::msTrigger_ 排序( DelayedMessage 类定义了 operator<)的队列中,按触发时间排序。如果多个延迟消息的触发时间相同,响应顺序按先进先出(FIFO)原则。

  • cmsDelay_:延迟多久触发消息,仅作调试使用
  • msTrigger_:触发消息的时间
  • num_:添加消息的时间
  • msg_:消息本身

在使用延迟消息时,不需要自行构建 DelayedMessage 实例。直接调用 MessageQueue::PostDelayed 或者 MessageQueue::PostAt 函数即可。

三、MessageData

MessageData相关内容包括 MessageData 类以及多个它的子类和几个工具函数。这些类和函数实现都很简单也很类似,这里就不详细说着,重点说一下这些类和方法的特性的功能:

1,MessageData和它的子类

  • MessageData
    定义了基类,并将析构函数定义为虚函数。
  • TypedMessageData
    使用模板定义的 MessageData 的一个子类,便于扩展。
  • ScopedMessageData
    类似于 TypedMessageData,用于指针类型。在析构函数中,自动对该指针调用 delete。
  • ScopedRefMessageData
    类似于 TypedMessageData,用于引用计数的指针类型。
  • DisposeData
    这是一个很特殊的消息,用以将某个对象交给消息引擎销毁。可能的用途有 2 个:
    有些函数不便在当前函数范围内销毁对象,见范例 HttpServer::Connection::~Connection;
    某对象属于某一线程,因此销毁操作应该交给所有者线程(未见范例)。WebRTC 用户不需要自行使用该类,调用 MessageQueue::Dispose 函数即可使用它的功能。

2,函数

 

  • WrapMessageData函数
    模板函数,便于创建 TypedMessageData
  • UseMessageData函数
    模板函数,用于将 TypedMessageData 中的 Data 取出

四、MessageQueue

  • 实现了消息一个完整地消息队列,该队列包括立即执行消息队列、延迟执行消息队列和具有优先级的消息队列。
  • MessageQueue 类也是 Thread 类的基类。
  • 所有的 WebRTC 的线程都是支持消息队列的。

1,头文件

MessageQueue的实现同样位于rtc_base/message_queue.h以及rtc_base/message_queue.cc中,要注意看头文件的注释帮助理解,头文件如下:


class RTC_EXPORT MessageQueue {
 public:
  static const int kForever = -1;

  // Create a new MessageQueue and optionally assign it to the passed
  // SocketServer. Subclasses that override Clear should pass false for
  // init_queue and call DoInit() from their constructor to prevent races
  // with the MessageQueueManager using the object while the vtable is still
  // being created.
  MessageQueue(SocketServer* ss, bool init_queue);
  MessageQueue(std::unique_ptr ss, bool init_queue);

  // NOTE: SUBCLASSES OF MessageQueue THAT OVERRIDE Clear MUST CALL
  // DoDestroy() IN THEIR DESTRUCTORS! This is required to avoid a data race
  // between the destructor modifying the vtable, and the MessageQueueManager
  // calling Clear on the object from a different thread.
  virtual ~MessageQueue();

  SocketServer* socketserver();

  // Note: The behavior of MessageQueue has changed.  When a MQ is stopped,
  // futher Posts and Sends will fail.  However, any pending Sends and *ready*
  // Posts (as opposed to unexpired delayed Posts) will be delivered before
  // Get (or Peek) returns false.  By guaranteeing delivery of those messages,
  // we eliminate the race condition when an MessageHandler and MessageQueue
  // may be destroyed independently of each other.
  virtual void Quit();
  virtual bool IsQuitting();
  virtual void Restart();
  // Not all message queues actually process messages (such as SignalThread).
  // In those cases, it's important to know, before posting, that it won't be
  // Processed.  Normally, this would be true until IsQuitting() is true.
  virtual bool IsProcessingMessagesForTesting();

  // Get() will process I/O until:
  //  1) A message is available (returns true)
  //  2) cmsWait seconds have elapsed (returns false)
  //  3) Stop() is called (returns false)
  virtual bool Get(Message* pmsg,
                   int cmsWait = kForever,
                   bool process_io = true);
  virtual bool Peek(Message* pmsg, int cmsWait = 0);
  virtual void Post(const Location& posted_from,
                    MessageHandler* phandler,
                    uint32_t id = 0,
                    MessageData* pdata = nullptr,
                    bool time_sensitive = false);
  virtual void PostDelayed(const Location& posted_from,
                           int cmsDelay,
                           MessageHandler* phandler,
                           uint32_t id = 0,
                           MessageData* pdata = nullptr);
  virtual void PostAt(const Location& posted_from,
                      int64_t tstamp,
                      MessageHandler* phandler,
                      uint32_t id = 0,
                      MessageData* pdata = nullptr);
  // TODO(honghaiz): Remove this when all the dependencies are removed.
  virtual void PostAt(const Location& posted_from,
                      uint32_t tstamp,
                      MessageHandler* phandler,
                      uint32_t id = 0,
                      MessageData* pdata = nullptr);
  virtual void Clear(MessageHandler* phandler,
                     uint32_t id = MQID_ANY,
                     MessageList* removed = nullptr);
  virtual void Dispatch(Message* pmsg);
  virtual void ReceiveSends();

  // Amount of time until the next message can be retrieved
  virtual int GetDelay();

  bool empty() const { return size() == 0u; }
  size_t size() const {
    CritScope cs(&crit_);  // msgq_.size() is not thread safe.
    return msgq_.size() + dmsgq_.size() + (fPeekKeep_ ? 1u : 0u);
  }

  // Internally posts a message which causes the doomed object to be deleted
  template 
  void Dispose(T* doomed) {
    if (doomed) {
      Post(RTC_FROM_HERE, nullptr, MQID_DISPOSE, new DisposeData(doomed));
    }
  }

  // When this signal is sent out, any references to this queue should
  // no longer be used.
  sigslot::signal0<> SignalQueueDestroyed;

 protected:
  class PriorityQueue : public std::priority_queue {
   public:
    container_type& container() { return c; }
    void reheap() { make_heap(c.begin(), c.end(), comp); }
  };

  void DoDelayPost(const Location& posted_from,
                   int64_t cmsDelay,
                   int64_t tstamp,
                   MessageHandler* phandler,
                   uint32_t id,
                   MessageData* pdata);

  // Perform initialization, subclasses must call this from their constructor
  // if false was passed as init_queue to the MessageQueue constructor.
  void DoInit();

  // Does not take any lock. Must be called either while holding crit_, or by
  // the destructor (by definition, the latter has exclusive access).
  void ClearInternal(MessageHandler* phandler,
                     uint32_t id,
                     MessageList* removed) RTC_EXCLUSIVE_LOCKS_REQUIRED(&crit_);

  // Perform cleanup; subclasses must call this from the destructor,
  // and are not expected to actually hold the lock.
  void DoDestroy() RTC_EXCLUSIVE_LOCKS_REQUIRED(&crit_);

  void WakeUpSocketServer();

  bool fPeekKeep_;
  Message msgPeek_;
  MessageList msgq_ RTC_GUARDED_BY(crit_);
  PriorityQueue dmsgq_ RTC_GUARDED_BY(crit_);
  uint32_t dmsgq_next_num_ RTC_GUARDED_BY(crit_);
  CriticalSection crit_;
  bool fInitialized_;
  bool fDestroyed_;

 private:
  volatile int stop_;

  // The SocketServer might not be owned by MessageQueue.
  SocketServer* const ss_;
  // Used if SocketServer ownership lies with |this|.
  std::unique_ptr own_ss_;

  RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(MessageQueue);
};

2,成员变量

状态相关:  
fInitialized_ 已经初始化
fDestroyed_ 已经销毁
stop_ 是否已经停止工作,不继续接受处理消息
fPeekKeep_ 是否存在一个被Peek出来的消息
消息相关:  
msgPeek_ 存储被Peek出来的一个即时消息
msgq_ 即时消息列表,存储即时消息,先入先出
dmsgq_ 延迟消息列表,存储延迟消息,按触发时间排序
dmsgq_next_num_ 下一个延迟消息的序号
Socket:  
ss_ 持有SocketServer类,用以完成IO多路复用操作
own_ss_ 与ss_一样,只是经过转移语句,使得该SocketServer对象只由该MessageQueue自己持有

3,核心方法


MQ的行为状态:
MessageQueue的行为变化。 当MQ停止时,Posts和Sends将会失败,但是已经成功投递的消息仍将在Get(或Peek)返回false之前进行传递。 通过保证这些消息的传递,我们消除了MessageHandler和MessageQueue可能彼此独立销毁时的竞争状况,两者的销毁是独立分开的。同时并非所有的MQ都会处理消息,比如SignalThread线程。在这种情况下,为了确定投递的消息是否会被处理,应该使用IsProcessingMessagesForTesting()探知下。

.h
// Note: The behavior of MessageQueue has changed.  When a MQ is stopped,
// futher Posts and Sends will fail.  However, any pending Sends and *ready*
// Posts (as opposed to unexpired delayed Posts) will be delivered before
// Get (or Peek) returns false.  By guaranteeing delivery of those messages,
// we eliminate the race condition when an MessageHandler and MessageQueue
// may be destroyed independently of each other.
virtual void Quit();
virtual bool IsQuitting();
virtual void Restart();
// Not all message queues actually process messages (such as SignalThread).
// In those cases, it's important to know, before posting, that it won't be
// Processed.  Normally, this would be true until IsQuitting() is true.
virtual bool IsProcessingMessagesForTesting();


.cc
void MessageQueue::Quit() {
  AtomicOps::ReleaseStore(&stop_, 1);
  WakeUpSocketServer();
}

bool MessageQueue::IsQuitting() {
  return AtomicOps::AcquireLoad(&stop_) != 0;
}

bool MessageQueue::IsProcessingMessagesForTesting() {
  return !IsQuitting();
}

void MessageQueue::Restart() {
  AtomicOps::ReleaseStore(&stop_, 0);
}

消息获取Peek和Get:
Peek(): 首先检查是否已经Peek过一个MSG到成员msgPeek_中,有则直接返回;若没有,则通过Get()方法从消息队列中取出一个消息,成功则将该消息交给msgPeek_成员,并将fPeekKeep_标志置为true。
Get():会阻塞的处理IO,直到有消息可以处理或者cmsWait时间已经过去或者Stop()方法被调用。详细的过程我在下边注释解释。

 .h
 // Get() will process I/O until:
  //  1) A message is available (returns true)
  //  2) cmsWait seconds have elapsed (returns false)
  //  3) Stop() is called (returns false)
  virtual bool Get(Message* pmsg,
                   int cmsWait = kForever,
                   bool process_io = true);
  virtual bool Peek(Message* pmsg, int cmsWait = 0);


.cc
bool MessageQueue::Peek(Message* pmsg, int cmsWait) {
  if (fPeekKeep_) {
    *pmsg = msgPeek_;
    return true;
  }
  if (!Get(pmsg, cmsWait))
    return false;
  msgPeek_ = *pmsg;
  fPeekKeep_ = true;
  return true;
}

bool MessageQueue::Get(Message* pmsg, int cmsWait, bool process_io) {
  // Return and clear peek if present
  // Always return the peek if it exists so there is Peek/Get symmetry

  if (fPeekKeep_) {//是否存在一个Peek过的消息没有被处理,存在则优先处理该消息
    *pmsg = msgPeek_;
    fPeekKeep_ = false;
    return true;
  }

  // Get w/wait + timer scan / dispatch + socket / event multiplexer dispatch

  int64_t cmsTotal = cmsWait;
  int64_t cmsElapsed = 0;
  int64_t msStart = TimeMillis();
  int64_t msCurrent = msStart;
  while (true) {
    // Check for sent messages  检查是否有send消息,若存在,先阻塞处理send消息
    ReceiveSends();

    // Check for posted events  检查所有post消息(即时消息+延迟消息)
    int64_t cmsDelayNext = kForever;
    bool first_pass = true;
    while (true) {
      // All queue operations need to be locked, but nothing else in this loop
      // (specifically handling disposed message) can happen inside the crit.
      // Otherwise, disposed MessageHandlers will cause deadlocks.
      {//所有队列操作都需要上锁
        CritScope cs(&crit_);
        // On the first pass, check for delayed messages that have been
        // triggered and calculate the next trigger time.
        //第一次时,检查是否已触发延迟的消息并计算下一个触发时间。
        if (first_pass) {
          first_pass = false;
          //将延迟消息队列dmsgq_中已经超过触发时间的消息全部取出放入到即时消息队列msgq_中
          // 计算当前时间距离下一个最早将要到达触发时间的消息还有多长时间cmsDelayNext。
          while (!dmsgq_.empty()) {
            if (msCurrent < dmsgq_.top().msTrigger_) {
              cmsDelayNext = TimeDiff(dmsgq_.top().msTrigger_, msCurrent);
              break;
            }
            msgq_.push_back(dmsgq_.top().msg_);
            dmsgq_.pop();
          }
        }
        // Pull a message off the message queue, if available.从即时消息队列取消息
        if (msgq_.empty()) {
          break;
        } else {
          *pmsg = msgq_.front();
          msgq_.pop_front();
        }
      }  // crit_ is released here.

      // Log a warning for time-sensitive messages that we're late to deliver.
      // 如果消息对时间敏感,那么如果超过了最大忍耐时间kMaxMsgLatency才被处理
      // 则打印警告日志
      if (pmsg->ts_sensitive) {
        int64_t delay = TimeDiff(msCurrent, pmsg->ts_sensitive);
        if (delay > 0) {
          RTC_LOG_F(LS_WARNING)
              << "id: " << pmsg->message_id
              << "  delay: " << (delay + kMaxMsgLatency) << "ms";
        }
      }
      // If this was a dispose message, delete it and skip it.
      // 如果取出是需要销毁消息,则销毁该消息
      if (MQID_DISPOSE == pmsg->message_id) {
        RTC_DCHECK(nullptr == pmsg->phandler);
        delete pmsg->pdata;
        *pmsg = Message();
        continue;
      }
      return true;
    }
    //此处说明没有消息需要处理了
    if (IsQuitting())
      break;

    // Which is shorter, the delay wait or the asked wait?

    int64_t cmsNext;
    if (cmsWait == kForever) {//如果状态是无限期,那么距离下个延迟消息的时间就作为本次IO处理时间
      cmsNext = cmsDelayNext;
    } else {//如果有超时时间,计算本次IO处理时间
      cmsNext = std::max(0, cmsTotal - cmsElapsed);// 总体来说还剩多少时间
      if ((cmsDelayNext != kForever) && (cmsDelayNext < cmsNext))
        cmsNext = cmsDelayNext;// 总体剩余时间和下一个延迟消息触发时间谁先到达?取其短者
    }

    {
      // Wait and multiplex in the meantime 阻塞处理IO多路复用
      if (!ss_->Wait(static_cast(cmsNext), process_io))
        return false;
    }

    // If the specified timeout expired, return 计算是否所有时间都已耗尽,是否进入下一个大循环

    msCurrent = TimeMillis();
    cmsElapsed = TimeDiff(msCurrent, msStart);
    if (cmsWait != kForever) {
      if (cmsElapsed >= cmsWait)
        return false;
    }
  }
  return false;
}

消息的投递:

MQ中消息投递相关的函数有这么几个:Post(),PostDelayed(),两个PostAt(),DoDelayPost()。其中Post()用于投递即时消息;PostDelayed(),两个PostAt()用于投递延迟消息,内部都是调用DoDelayPost()来实现。

Post(): 即时消息的投递,源码如下。主要就是将函数的入参封装成一个即时消息Message对象,然后放置到即时队列msgq_的队尾。需要注意的有四点:

  • 如果消息循环已经处理停止状态,即stop_状态值不为0,那么消息循环拒绝消息入队,消息数据会被释放掉。此时,投递消息者是不知情的。
  • 如果消息对时间敏感,即想知道该消息是否即时被处理了,最大延迟不超过kMaxMsgLatency 150ms;
  • 为了线程安全,队列的入队操作是需要加锁的,CritScope cs(&crit_)对象的构造和析构确保了这点;
  • 消息入队后,由于处理消息是首要任务,因此,需要调用WakeUpSocketServer()使得IO多路复用的处理赶紧返回,即调用ss_->WakeUp()方法实现。由于这块儿是IO多路复用实现内容,后续会专门写文章分析,此处只要知道该方法能够使得阻塞式的IO多路复用能结束阻塞,回到消息处理上来即可。

PostDelayed(),PostAt(),DoDelayPost(): 延迟消息的投递,源码如下。PostDelayed(),PostAt()均是将各自的入参稍作转换后,再调用DoDelayPost()方法,将入参封装成延迟消息DelayedMesssage,然后加入到延迟消息队列dmsgq_中,并从IO多路复用的阻塞中唤醒来处理消息。与Post()方法中的做法并无二致。需要额外注意的地方有这么几点:

延迟消息的序号计算,成员dmsgq_next_num_是一个uint32_t类型的数据,也即处理4,294,967,296条消息后会溢出回归到0,此时,优先级队里中消息的排序可能会受到影响。但是考虑到一点,正如源码注释上解释的那样:优先级队里中的消息会优先按照触发时间排序,那么最多影响到的不过是那些触发时间相同的消息而已。即便是影响到了部分触发时间相同的消息,那也不过是很短的时间,并不会造成很大的影响。


void MessageQueue::Post(const Location& posted_from,
                        MessageHandler* phandler,
                        uint32_t id,
                        MessageData* pdata,
                        bool time_sensitive) {
  if (IsQuitting()) {
    delete pdata;
    return;
  }

  // Keep thread safe
  // Add the message to the end of the queue
  // Signal for the multiplexer to return

  {
    CritScope cs(&crit_);
    Message msg;
    msg.posted_from = posted_from;
    msg.phandler = phandler;
    msg.message_id = id;
    msg.pdata = pdata;
    if (time_sensitive) {
      msg.ts_sensitive = TimeMillis() + kMaxMsgLatency;
    }
    msgq_.push_back(msg);
  }
  WakeUpSocketServer();
}

void MessageQueue::PostDelayed(const Location& posted_from,
                               int cmsDelay,
                               MessageHandler* phandler,
                               uint32_t id,
                               MessageData* pdata) {
  return DoDelayPost(posted_from, cmsDelay, TimeAfter(cmsDelay), phandler, id,
                     pdata);
}

void MessageQueue::PostAt(const Location& posted_from,
                          uint32_t tstamp,
                          MessageHandler* phandler,
                          uint32_t id,
                          MessageData* pdata) {
  // This should work even if it is used (unexpectedly).
  int64_t delay = static_cast(TimeMillis()) - tstamp;
  return DoDelayPost(posted_from, delay, tstamp, phandler, id, pdata);
}

void MessageQueue::PostAt(const Location& posted_from,
                          int64_t tstamp,
                          MessageHandler* phandler,
                          uint32_t id,
                          MessageData* pdata) {
  return DoDelayPost(posted_from, TimeUntil(tstamp), tstamp, phandler, id,
                     pdata);
}

void MessageQueue::DoDelayPost(const Location& posted_from,
                               int64_t cmsDelay,
                               int64_t tstamp,
                               MessageHandler* phandler,
                               uint32_t id,
                               MessageData* pdata) {
  if (IsQuitting()) {
    delete pdata;
    return;
  }

  // Keep thread safe
  // Add to the priority queue. Gets sorted soonest first.
  // Signal for the multiplexer to return.

  {
    CritScope cs(&crit_);
    Message msg;
    msg.posted_from = posted_from;
    msg.phandler = phandler;
    msg.message_id = id;
    msg.pdata = pdata;
    DelayedMessage dmsg(cmsDelay, tstamp, dmsgq_next_num_, msg);
    dmsgq_.push(dmsg);
    // If this message queue processes 1 message every millisecond for 50 days,
    // we will wrap this number.  Even then, only messages with identical times
    // will be misordered, and then only briefly.  This is probably ok.
    ++dmsgq_next_num_;
    RTC_DCHECK_NE(0, dmsgq_next_num_);
  }
  WakeUpSocketServer();
}

消息处理Dispatch():

Dispatch()方法,记录消息处理的开始时间start_time;调用消息的MessageHandler的OnMessage方法进行消息处理;记录消息处理的结束时间end_time;计算消息处理花费了多长时间diff,如果消息花费时间过程,超过kSlowDispatchLoggingThreshold(50ms),则打印一条警告日志,告知从哪儿构建的消息花费了多长时间才消费完。

void MessageQueue::Dispatch(Message* pmsg) {
  TRACE_EVENT2("webrtc", "MessageQueue::Dispatch", "src_file_and_line",
               pmsg->posted_from.file_and_line(), "src_func",
               pmsg->posted_from.function_name());
  int64_t start_time = TimeMillis();
  pmsg->phandler->OnMessage(pmsg);
  int64_t end_time = TimeMillis();
  int64_t diff = TimeDiff(end_time, start_time);
  if (diff >= kSlowDispatchLoggingThreshold) {
    RTC_LOG(LS_INFO) << "Message took " << diff
                     << "ms to dispatch. Posted from: "
                     << pmsg->posted_from.ToString();
  }
}

消息清空Clear():

  • MQ中消息可能存在的位置有3个:Peek消息msgPeek_,即时消息队列msgq_,延迟消息队列dmsgq_;因此,需要从这3个地方去挨个查找能匹配的消息。
  • 如果Clear()方法传入了一个MessageList* removed,匹配的消息都会进入该list;若是没有传入这样一个list,那么消息数据都将会立马销毁。
     
void MessageQueue::Clear(MessageHandler* phandler,
                         uint32_t id,
                         MessageList* removed) {
  CritScope cs(&crit_);
  ClearInternal(phandler, id, removed);
}

void MessageQueue::ClearInternal(MessageHandler* phandler,
                                 uint32_t id,
                                 MessageList* removed) {
  // Remove messages with phandler

  if (fPeekKeep_ && msgPeek_.Match(phandler, id)) {
    if (removed) {
      removed->push_back(msgPeek_);
    } else {
      delete msgPeek_.pdata;
    }
    fPeekKeep_ = false;
  }

  // Remove from ordered message queue

  for (MessageList::iterator it = msgq_.begin(); it != msgq_.end();) {
    if (it->Match(phandler, id)) {
      if (removed) {
        removed->push_back(*it);
      } else {
        delete it->pdata;
      }
      it = msgq_.erase(it);
    } else {
      ++it;
    }
  }

  // Remove from priority queue. Not directly iterable, so use this approach

  PriorityQueue::container_type::iterator new_end = dmsgq_.container().begin();
  for (PriorityQueue::container_type::iterator it = new_end;
       it != dmsgq_.container().end(); ++it) {
    if (it->msg_.Match(phandler, id)) {
      if (removed) {
        removed->push_back(it->msg_);
      } else {
        delete it->msg_.pdata;
      }
    } else {
      *new_end++ = *it;
    }
  }
  dmsgq_.container().erase(new_end, dmsgq_.container().end());
  dmsgq_.reheap();
}

消息销毁Dispose()

想要销毁某个对象,而不方便立马销毁,那么就可以将调用消息循环的Dispose()方法让消息循环帮忙进行数据销毁。

  // Internally posts a message which causes the doomed object to be deleted
  template 
  void Dispose(T* doomed) {
    if (doomed) {
      Post(RTC_FROM_HERE, nullptr, MQID_DISPOSE, new DisposeData(doomed));
    }
  }

 

WebRTC学习进阶之路 --- 十五、源码分析之WebRTC中的线程详解-MessageQueueManager&MessageQueue&Message&MessageHandler_第1张图片

五、MessageHandler

消息处理的核心就是OnMessage ,为外部提供模板类,方便应用层实现消息接收的具体实现:

class RTC_EXPORT MessageHandler {
 public:
  virtual ~MessageHandler();
  virtual void OnMessage(Message* msg) = 0;

 protected:
  MessageHandler() {}

 private:
  RTC_DISALLOW_COPY_AND_ASSIGN(MessageHandler);
};

// Helper class to facilitate executing a functor on a thread.
template 
class FunctorMessageHandler : public MessageHandler {
 public:
  explicit FunctorMessageHandler(FunctorT&& functor)
      : functor_(std::forward(functor)) {}
  virtual void OnMessage(Message* msg) { result_ = functor_(); }
  const ReturnT& result() const { return result_; }

  // Returns moved result. Should not call result() or MoveResult() again
  // after this.
  ReturnT MoveResult() { return std::move(result_); }

 private:
  FunctorT functor_;
  ReturnT result_;
};

// Specialization for ReturnT of void.
template 
class FunctorMessageHandler : public MessageHandler {
 public:
  explicit FunctorMessageHandler(FunctorT&& functor)
      : functor_(std::forward(functor)) {}
  virtual void OnMessage(Message* msg) { functor_(); }
  void result() const {}
  void MoveResult() {}

 private:
  FunctorT functor_;
};


参考链接:https://blog.csdn.net/ice_ly000/article/details/103178697

WebRTC学习进阶之路系列总目录:https://blog.csdn.net/xiaomucgwlmx/article/details/103204274

你可能感兴趣的:(WebRTC学习进阶之路系列)