WebRTC源代码探索之旅——多线程篇-4

4 messagequeue

 

messagequeue.h/messagequeue.cc文件是多路信号分离器的重要组成部分。它实现了消息一个完整地消息队列,该队列包括立即执行消息队列、延迟执行消息队列和具有优先级的消息队列。其中,talk_base::MessageQueue类也是talk_base::Thread类的基类。所以,所有的WebRTC的线程都是支持消息队列的。

 

4.1 talk_base::MessageQueueManager

 

talk_base::MessageQueueManager类是一个全局单例类。这个类看似比较复杂,但是功能其实非常简单——仅仅为了在所有的talk_base::MessagerQueue中删除与指定的talk_base::MessageHandler相关的消息。WebRTC的消息队列在发送消息的时候要指定消息处理器(talk_base::MessageHandler)。如果某个消息处理器被析构,那么与之相关的所有消息都将无法处理。所以,创建了这个全局单例类来解决这个问题(见talk_base::MessageHandler析构函数)。

 

talk_base::MessageQueueManager的代码没有涉及任何跨平台的API调用,而且本身功能也非常简单。所以我就不讨论它如何使用std::vector管理talk_base::MessageQueue。唯一需要注意的就是talk_base::MessageQueueManager如何保证自己在第一个talk_base::Thread类实例化之前完成talk_base::MessageQueueManager全局单例的实例化。这当中有个有趣的状况,talk_base::MessageQueueManager在保证了自己必然在第一条子线程被创建之前自己被实例化,talk_base::MessageQueueManager:: Instance函数内部没有使用任何锁来保护talk_base::MessageQueueManager::instance_实例的创建。

 

如前面所说,talk_base::MessagerQueue是talk_base::Thread的基类。在创建talk_base::Thread时必然会调用talk_base::MessagerQueue的构造函数。在talk_base::MessagerQueue的构造函数中调用了talk_base::MessageQueueManager::Add函数,而该函数会使用talk_base::MessageQueueManager::Instance函数创建talk_base::MessagerQueueManager的实例。由于talk_base::ThreadManager保证了在创建第一个子线程之前,主线程会被包装成talk_base::Thread对象,所以talk_base::MessageQueueManager必然可以将主线程作为第一个talk_base::MessageQueue对象纳入管理。

 

以上的描述可能比较晦涩难懂,这是因为整个流程涉及到了talk_base::Thread和talk_base::ThreadManager等类。而这些都是尚未讲解过他们的代码。不过即使看不明白也没关系,我会在讲解完所有相关类之后演示2段范例代码,并将范例代码的调用栈完全展开。看过范例代码后绝大多数读者都应该能够明白talk_base::MessageQueueManager的原理。

 

talk_base::MessageQueueManager还有最后一个问题,那就是它什么时候被析构。talk_base::MessageQueue的析构函数会调用talk_base::MessageQueueManager::Remove函数,并且“理论上来说”在最后一个talk_base::MessageQueue从队列中移除之后会析构talk_base::MessageQueueManager。既然,所有的线程都被移除,那就意味着talk_base::MessageQueueManager实例在被delete时重新回到了单线程的环境,所以也没有任何锁的保护。

 

4.2 MessageData

 

这一节的内容将包括talk_base::MessageData类以及多个它的子类和几个工具函数。这些类和函数都很简单,所以就不介绍代码和原理,仅仅罗列一下它们的功能。

 

4.2.1 talk_base::MessageData

 

定义了基类,并将析构函数定义为虚函数。

 

4.2.2 talk_base::TypedMessageData

 

使用模板定义的talk_base::MessageData的一个子类,便于扩展。

 

4.2.3 talk_base::ScopedMessageData

 

类似于talk_base::TypedMessageData,用于指针类型。在析构函数中,自动对该指针调用delete。

 

4.2.4 talk_base::ScopedRefMessageData

 

类似于talk_base::TypedMessageData,用于引用计数的指针类型。

 

4.2.5 talk_base::WrapMessageData函数

 

模板函数,便于创建talk_base::TypedMessageData

 

4.2.6 talk_base::UseMessageData函数

 

模板函数,用于将talk_base::TypedMessageData中的Data取出

 

4.2.7 talk_base::DisposeData

 

这是一个很特殊的消息,用以将某个对象交给消息引擎销毁。可能的用途有2个:1. 有些函数不便在当前函数范围内销毁对象,见范例talk_base::HttpServer::Connection::~Connection;2.某对象属于某一线程,因此销毁操作应该交给所有者线程(未见范例)。WebRTC用户不需要自行使用该类,调用talk_base::MessageQueue::Dispose函数即可使用它的功能。

 

以上7个类或函数的实现非常简单,有C++使用经验的读者非常容易就能理解(标准库中就有相似的类)。


4.3 Message

 

这一节将简单介绍一下3个类:talk_base::Message、talk_base::DelayedMessage和talk_base::MessageList。

 

4.3.1 talk_base::Message

 

定义了消息的基本数据结构。

 

4.3.2 talk_base::DelayedMessage

 

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

 

这里我将简单介绍一下各个成员变量的用途:

cmsDelay_:延迟多久触发消息,仅作调试使用

msTrigger_:触发消息的时间

num_:添加消息的时间

msg_:消息本身

 

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

 

4.3.3 talk_base::MessageList

 

消息列表,定义为std::list<talk_base::Message>

4.4 talk_base::MessageQueue

 

现在我们正式进入多线程篇最为激动人心的部分——多路信号分离器的消息队列组件。WebRTC的多路型号分离器由2部分组成:消息队列和talk_base::SocketServer(主要实现就是talk_base::PhysicalSocketServer)。消息队列负责接受消息,并使用消息处理器(talk_base::MessageHandler的子类)处理消息。在处理完所有消息后,消息队列调用talk_base::SocketServer::Wait函数阻塞等待新的IO信号。如果有新的消息进入,消息队列会调用talk_base::SocketServer::WakeUp唤醒talk_base::SocketServer阻塞等待。这就是消息队列和talk_base::SocketServer协同工作的基本流程。

 

让我们先来看一下,消息队列的实现。消息队列的主要功能是接收和处理消息,并作为talk_base::Thread的基类出现。

 

它的主要部件(成员变量)包括:

ss_:协同工作的talk_base::SocketServer

default_ss_:默认的talk_base::SocketServer。如果在构造的时候提供talb_base::SocketServer,那么消息队列就使用用户提供的;如果没有提供,消息队列就初始化一个talk_base::PhysicalSocketServer并保存在default_ss_上,然后赋值给ss_。在消息队列析构的时候,如果default_ss_保存有默认构造talk_base::PhysicalSocketServer,那就销毁它

msgq_:消息队列,队列内的消息按照先进先出的原则立即执行

dmsgq_:延迟消息队列,队列内的消息按照指定的时间延迟执行

 

接着,我们来看一下talk_base::MessageQueue的主要成员函数:

talk_base::MessageQueue::Get:等待接收消息。

参数说明:

pmsg:存放消息的指针,用于返回消息

cmsWait:等待的时间,kForever表示无限等待

process_io:是否要求talk_base::SocketServer处理IO信号

 

talk_base::MessageQueue::PostDelayed:发送一个延迟消息(从当前时间计算延迟处理时间)

参数说明:

cmsDelay:延迟毫秒数

phandler:消息处理器(talk_base::MessageHandler的子类)

id:消息ID

pdata:MessageData指针

 

talk_base::MessageQueue::PostAt:发送一个延迟消息(直接指定延迟处理时间)

参数说明:

tstamp:消息触发的时间

phandler:消息处理器(talk_base::MessageHandler的子类)

id:消息ID

pdata:MessageData指针

 

talk_base::MessageQueue::Clear:通过指定talk_base::MessageHandler和消息ID删除消息

参数说明:

phandler:指定被删除消息的talk_base::MessageHandler

id:指定被删除消息的ID;如果该id为MQID_ANY所有与phandler相关的消息都将被删除

removed:返回所有被删除消息的列表

 

talk_base::MessageQueue::GetDelay:从现在到下一条将要触发的延迟消息的触发时间的毫秒数(无参数)

 

talk_base::MessageQueue::Dispose:请求消息引擎删除一个对象(delete对象指针)

参数说明:

doomed:将要被删除的对象的指针

 

talk_base::MessageQueue::SignalQueueDestroyed(signal slot):通知接收者(observer)消息队列将要删除(参数无)

 

talk_base::MessageQueue类的核心是talk_base::MessageQueue::Get函数。Get函数的主循环首先检查dmsgq_和msgq_是否有立即需要处理的消息。如果检查到需要立即处理的消息,就马上将该消息返回。如果没有检查到需要立即处理的消息,那么线程就阻塞等待在talk_base::SocketServer::Wait函数上。如果阻塞等待期间有新的消息进入队列或者线程需要停止退出,通过talk_base::SocketServer::WakeUp函数唤醒被阻塞的talk_base::SocketServer::Wait函数。从talk_base::SocketServer::Wait函数返回后,talk_base::MessageQueue::Get函数会重新检查是否有可以返回的消息,如果没有则再次阻塞等待在talk_base::SocketServer::Wait函数上。以上流程如下图所示:

 

 

talk_base::MessageQueue的原理大致如此。如果读者结合代码依然无法理解,我会在多线程篇的最后给出几段范例代码,并完整地展开所有函数的调用序列和调用栈。相信到时候大多数读者都能够理解WebRTC的线程架构是如何工作的。


发布了111 篇原创文章 · 获赞 175 · 访问量 126万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览