WEBRTC线程模型
使用线程模型主要用来解决多线程的问题,在多线程代码开发时经常会碰到多线程问题:如共同访问相同的资源导致的问题,此时需要采用锁进行处理,又比如线程功能不同,某些业务逻辑不允许阻塞,因此需要切换到合适的线程执行
WEBRTC作为在音视频领域内优秀的开源框架,提供了很好的线程管理机制,研究内部线程的设计可以让我们更好的了解WEBRTC的处理流程
下面来看一下几个线程使用的例子:
○ 异步任务:
场景:业务功能上有所区分,如网络线程接收到的数据包,需要发送到其他线程处理,不影响网络IO的读取,可整理为如下图:
解决方法:线程1将任务1抛给线程2执行,自己可以继续处理任务2
○ 同步任务:
场景:资源使用上存在区分,如线程使用过程中存在公共的资源,那么尽量使用同一个线程进行处理,避免使用锁来增加代码复杂度
解决方法:线程1将任务1抛给线程2执行,等待任务1执行完毕之后继续执行其他操作
WEBRTC中已经提供了上述两种情况下的线程处理模块,下面我们来一起分析一下。
WEBRTC线程模块
ProcessThread
我们先从比较简单的异步任务来进行看起,ProcessThread类主要提供了周期处理任务的能力,代码中使用的功能一般来讲是需要周期性处理的模块(如paceSender)
模块内部主要包含部件如下:
std::queue<rtc::QueuedTask> queue_; /* 任务队列,用于处理线程模块中任务 */
std::unique_ptr<rtc::PlatformThread> thread_; /* 线程类, 用于对平台线程功能的基本封装 平台实现采用pthread*/
conststd::unique_ptr<webrtc::EventWrapper> wake_up_; /* 线程消息变量, 用于控制线程唤醒 平台实现使用pthread条件变量 */
ModuleList modules_; /* 线程绑定的模块 */
使用方式:
使用前创建对应的ProcessThread类,对应模块继承Module类的process接口,将Module注册到对应的ProcessThread类中,即可运行对应线程
亮点:
- 从上面可以发现ProcessThread是可以支持多个模块任务注册到同一线程的,分离了模块和线程之前的关系,可自由的配置不同模块和线程的搭配
- 支持异步消息发送,可解决对应线程模块内锁的问题
缺点:
- 不支持同步调用模式,如果是同步调用需要加锁,对于控制逻辑比较多的模块不太友好
Thread
Thread类的功能更进一步,既包含同步线程相关的使用,也包含异步任务使用,Thread继承于MessageQueue类,MessageQueue主要用来提供任务处理的能力
Thread使用方式:
1、注册runable对象,执行runable对象的run方法
2、执行内部Thread的run方法,具备异步和同步处理功能
Thread/MessageQueue主要成员:
SocketServer* const ss_; /* 用于触发任务处理,类似信号量 */
MessageList msgq_ GUARDED_BY(crit_); / * 消息队列 */
PriorityQueue dmsgq_ GUARDED_BY(crit_); /* 延时消息队列 */
异步调用:
主要使用MessageQueue 中的Post接口
处理位于MessageQueue::Get方法中,会对msg进行消费,进而处理异步消息
同步调用:
主要使用Thread中的invoke和send接口:
Send第一步会判断是否位于当前任务是否当前线程中进行发送的,如果是直接进行处理
如果不是当前线程中,那么会推入sendlist_中,唤醒socketserver以开启MessageQueue中任务的检查执行,随后等到线程处理完毕,在此同时需要获取到当前线程的运行具柄,继续执行当前线程的任务,保证当前线程需要处理的同步任务不卡住,当目标任务执行完毕后返回
优点:
1、既支持同步任务,也支持异步任务,同时支持自定义的线程运行方式
缺点:
1、无法进行线程绑定,每个Thread类只能处理一个模块功能,使用外置模块run方式的时候不可以使用任务机制
总结
通过以上介绍可以得出:
1、Thread类更适合业务逻辑的处理,ProcessThread更适合媒体逻辑的处理
2、在业务处理过程中使用Thread可以大量的减少锁的使用,是一个很好的设计