WebRTC源码分析-线程基础之MessageQueueManager

目录

前言

MessageQueueManager的构造

MessageQueue的添加与移除

清理

处理所有MQ中的消息

总结


前言

正如其名,MessageQueueManager类(后续简写为MQM)提供了MessageQueue(简写为MQ)的管理功能。在之前的文章中已经分析过,MQ在构建时会调用MQ.DoInit()方法,该方法将MQ添加到MQM的内部std::Vector<MessageQueue*>成员中。
MQM类的声明和定义分别在rtc_base/message_queue.h以及rtc_base/message_queue.cc中,其定义如下所示

// MessageQueueManager does cleanup of of message queues
class 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<MessageQueue*> 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的构造

MessageQueueManager的构造方式与ThreadManager一样,都是单例模式,都是非安全的。之前分析过ThreadManager为什么能够安全的构造,MessageQueueManager原理一样,并且MessageQueueManager对象的创建先于第一个MessageQueue对象。

MessageQueueManager* MessageQueueManager::Instance() {
  static MessageQueueManager* const instance = new MessageQueueManager;
  return instance;
}
MessageQueueManager::MessageQueueManager() : processing_(0) {}
MessageQueueManager::~MessageQueueManager() {}

MessageQueue的添加与移除

MessageQueueManager提供了Add与Remove的静态函数来往单例的管理类中添加和删除MQ,具体如下源码所示:

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<MessageQueue*>::iterator iter;
    iter = std::find(message_queues_.begin(), message_queues_.end(),
                     message_queue);
    if (iter != message_queues_.end()) {
      message_queues_.erase(iter);
    }
  }
}

添加和删除方法对外都是以静态方法提供,通过调用MQM的单实例的对应的私有方法来实现往向量Vector中添加和删除MQ,需要说明的注意点有以下几个:
1)成员crit_是临界区类CriticalSection的对象,该成员保证多线程环境下MQM.message_queues_以及MQM.processing_访问安全,正如上面两个函数所示,函数开头创建CritScope cs(&crit_); 在cs的构造函数中调用crit_->Enter()表示进入临界区,相当于上锁。利用函数结束后cs对象的析构中调用crit_->Leave()表示离开临界区,相当于解锁。
2)存储MQ的向量声明为:std::vector<MessageQueue*> message_queues_ RTC_GUARDED_BY(crit_);
其中RTC_GUARDED_BY宏在clang编译器下展开为attribute(guarded_by(crit_)),指示编译器在编译过程中检查代码中所有访问message_queues_的各个路径上是否都先获取了锁crit_,如果没有就会在编译过程中产生错误或者警告。而对于其他编译器,该宏不起任何作用,意味着不会在编译期进行检查。详见 Thread Safety Analysis
3)成员processing_ 声明为: size_t processing_ RTC_GUARDED_BY(crit_); Add与Remove函数中执行了RTC_DCHECK_EQ(processing_, 0)断言,必须确保processing_ 为0。当processing_不为0时,要么在执行MQM的Clear()方法,要么在执行ProcessAllMessageQueues(),这些操作此时,是不允许往MQM添加MQ或者删除MQ这种会改变Vector列表的操作,因为前面的两个函数一般都会要遍历Vector。思考一点,不是已经上锁保证线程安全了嘛,为啥还要保证processing_为0呢?继续往下看吧~~

清理

与Add和Remove方式一摸一样,Clear函数也是以静态方法的形式对外提供。Clear函数的作用是从MQM所管理的所有MQ中删除与入参MessageHandler* handler匹配消息。具体而言就是遍历MQM中的MQ,然后调用MQ本身的Clear()方法,这个方法比较冗长,此处就不展开叙述,将会在介绍MQ的文章中详细描述。

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

另外很重要的一点,该方法并没有使用前文所述的CritScope cs(&crit_)来实现线程安全,而是使用了一个新的类MarkProcessingCritScope cs(&crit_, &processing_),有什么神奇的地方?且看源码:

class RTC_SCOPED_LOCKABLE MarkProcessingCritScope {
 public:
  MarkProcessingCritScope(const CriticalSection* cs, size_t* processing)
      RTC_EXCLUSIVE_LOCK_FUNCTION(cs)
      : cs_(cs), processing_(processing) {
    cs_->Enter();
    *processing_ += 1;
  }

  ~MarkProcessingCritScope() RTC_UNLOCK_FUNCTION() {
    *processing_ -= 1;
    cs_->Leave();
  }

 private:
  const CriticalSection* const cs_;
  size_t* processing_;

  RTC_DISALLOW_COPY_AND_ASSIGN(MarkProcessingCritScope);
};

首先要知道CriticalSection是可重入的,也即一个线程上调用cs_->Enter()上锁之后,在释放锁之前,同一个线程可以反复调用cs_->Enter()而不会阻塞,因此被称为“可重入锁”。同一个线程上锁一次,processing_就增1,记录上锁次数,只要processing_不为0,表示我正在Clear操作或者后文的Process*方法,这两个方法不会改变MQM中Vector列表,因此,可以在解锁之前,重入进行反复操作,但是不允许Add和Remove操作,因为其会改变Vector,这就是为什么Add和Remove函数中既加锁了,还要断言processing_必须为0,否则代码就是写得有Bug了。

处理所有MQ中的消息

这个方法目前还没有完全的理解,首先说下我自己的分析。

  • 方法如其名,目标在于使得MQM中管理的MQ中的消息得到处理。
  • 当某个线程调用该方法时,会遍历所有的MQ,然后向MQ中投递一个消息ID为MQID_DISPOSE的延迟消息,其消息数据为ScopedIncrement对象,ScopedIncrement的构造中将queues_not_done原子性自增1,表示该消息队列中有消息没有被处理,而该延迟消息时间为0,那么该延迟消息将进入MQ的延迟消息队列的队首(因为MQ的延迟消息队列是以延迟时间排序的优先级队列)。记住,所有的MQ中都会投递一个这样的消息。
  • 方法后续就是获取调用该方法的线程所关联的Thread对象,并通过Thread对象的ProcessMessages方法不断的从当前线程的MQ中取出消息进行处理,直到queues_not_done为0,此时,之前投递到消息循环中的MQID_DISPOSE类别的消息得到处理,因为该消息在消息循环中被取出后,消息数据ScopedIncrement对象会被直接delete,从而ScopedIncrement析构完成queues_not_done原子性自减1。此时,表征着该消息循环中的所有即时消息都得到了处理。
  • 最大的疑惑就是,所有的MQ中都放入了一个MQID_DISPOSE类别的延迟消息,调用该方法的线程会如上所述的方式阻塞地将所有即时消息处理掉,而没有主动调用该方法的线程是如何保证该方法的目标得以实现的,即,使得所有MQ的即时消息都能同步的立马处理?毕竟,对于其他的MQ此处也仅仅是投递了一个MQID_DISPOSE类别的延迟消息而已。
static void ProcessAllMessageQueues() { ProcessAllMessageQueuesForTesting(); }

void MessageQueueManager::ProcessAllMessageQueuesForTesting() {
  return Instance()->ProcessAllMessageQueuesInternal();
}

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

总结

  • MessageQueueManager是管理MessageQueue的类,与ThreadManager管理Thread的方式是不同的。MQM内部的向量成员std::Vector<MessageQueue*> message_queues_用于存储所有的MQ,时机是在MQ的构造函数中调用MQM的Add静态方法将自身指针放入message_queues_中。
  • MQM对外提供了所有方法都是静态方法,这些静态方法均是调用MQM单实例的私有的同名函数来实现添加,删除MQ,清除所有MQ中含有某个MessageHandler的所有消息,处理所有MQ中的消息。
  • 为了线程安全,MQM中的所有操作都由CriticalSection crit_来提供上锁的操作,该对象对外暴露的是Windows上临界区概念的API,其提供了Enter(),Leave()等进出临界区的方法。CriticalSection跨平台是如何实现的?请看WebRTC源码分析-线程安全之CriticalSection。 实践上,在需要上锁的地方创建一个CritScope cs(&crit_)局部对象,这个cs局部对象的构造函数中调用crit_->Enter()进行上锁,利用局部对象在{}之后的析构中调用crit_->Leave()来解锁。
  • 为了给MQM的Clear和Process* 方法提供可重入访问方法,又需要与Add和Remove方法互斥,因此,在Clear和Procees* 方法中使用MarkProcessingCritScope来提供上锁计数processing_,Add和Remove中断言processing_来确保操作安全。
  • 关于Procees* 如何使得所有线程的消息得到处理的疑惑,可能是局限于MQM类的分析无法全局看到实现机制,当前只知道调用该方法的线程是如何达到处理所有即时消息的。而相关的方法本文没有展开来说,会在介绍Thread和MQ的文章中详细阐述。如果谁理解了该函数如何使得其他线程处理所有即时消息的机制,希望留言。
  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: webrtc-qt-example是一个基于Qt框架开发的WebRTC示例项目。 WebRTC是一种开的实时通信技术,能够支持音频、视频和数据的实时传输。它通过浏览器之间的端对端连接,实现了高质量的实时通信。 webrtc-qt-example的目的是展示如何使用Qt进行WebRTC开发。Qt是一套跨平台的C++应用程序开发框架,它提供了丰富的工具和库,使开发者能够快速构建可靠的应用程序。 这个示例项目提供了一些基本的功能和界面,使开发者能够了解和学习如何将WebRTC集成到Qt应用程序中。它包含了常见的WebRTC功能,如媒体流捕获、媒体流传输、信令交换等。 通过webrtc-qt-example,开发者可以学习到如何使用Qt的多媒体模块来捕获音频、视频和媒体设备。同时,也可以学习到如何使用Qt的网络模块来进行实时信令交换和流传输。 这个示例项目还提供了一些简单的界面,方便开发者进行测试和调试。开发者可以通过该界面实现与其他WebRTC应用的通信,例如建立视频通话、音频通话等。 总之,webrtc-qt-example是一个非常实用的示例项目,可以帮助开发者快速上手并掌握WebRTC在Qt中的开发。 ### 回答2: webrtc-qt-example是一个基于Qt框架的WebRTC示例应用程序。WebRTC是一种开项目,它提供了在浏览器之间进行实时通信的能力,包括视频和音频的传输。而webrtc-qt-example则是将这种技术集成到Qt应用程序中的一个示例。 在webrtc-qt-example中,它使用了Qt的多媒体框架和WebRTC提供的API来实现音视频的传输和显示。通过使用WebRTC的API,webrtc-qt-example可以建立点对点的连接,进行音频和视频的实时传输。 webrtc-qt-example中的代码结构清晰,易于理解和扩展。它提供了一些基本的功能,如建立连接、发送和接收音视频流、呼叫取消等。开发者可以根据自己的需求来对这些功能进行定制和扩展。 此外,webrtc-qt-example还支持一些高级特性,如媒体设备的选择、音视频的编码和解码等。开发者可以通过修改代码来选择不同的媒体设备,并且可以使用不同的编码和解码算法来满足自己的需求。 总之,webrtc-qt-example是一个很棒的WebRTC示例应用程序,它可以帮助开发者快速了解和使用WebRTC技术。无论是为了实现实时视频通话、视频会议还是其他需要音视频传输的应用场景,webrtc-qt-example都提供了一个良好的起点,帮助开发者快速上手并实现自己的需求。 ### 回答3: webrtc-qt-example是一个基于Qt框架和WebRTC技术的示例应用。WebRTC是一种用于在Web浏览器上实现实时通信的开项目,它提供了一套丰富的API和协议,可以实现音视频通话、数据传输以及屏幕共享等功能。 webrtc-qt-example利用Qt框架提供的跨平台能力,结合WebRTC技术,展示了在Qt应用中如何实现实时通信功能。这个示例应用具有以下特点和功能: 1. 界面友好:webrtc-qt-example使用Qt的GUI绘制工具,具有美观、直观的用户界面,便于用户操作和使用。 2. 实时通信:webrtc-qt-example内置了WebRTC的音视频通信功能,可以实现实时的语音和视频通话,支持两个或多个用户之间的通信。 3. 数据传输:除了音视频通话,webrtc-qt-example还支持在通话中传输数据。可以通过编写代码,实现实时文本传输或共享文件等功能。 4. 屏幕共享:webrtc-qt-example还支持屏幕共享功能,可以将自己的屏幕内容分享给其他用户,实现远程协助或在线教育等应用场景。 通过webrtc-qt-example的学习和实践,开发者可以了解并深入理解WebRTC技术的使用方法,以及在Qt框架中的应用。同时,借助webrtc-qt-example提供的示例代码和API文档,开发者可以进一步开发出更加复杂和功能丰富的实时通信应用,满足不同领域的需求。 总之,webrtc-qt-example是一个基于Qt框架和WebRTC技术的示例应用,具备实时音视频通话、数据传输和屏幕共享等功能,适用于开发者学习、实践和开发基于WebRTC的实时通信应用程序。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值