引用计数是计算机编程语言中的一种内存管理技术,是指将资源(可以是对象、内存、或磁盘空间等等)的被引用次数保存起来,当被引用次数变为零时就将其释放的过程。
使用引用计数技术可以实现自动资源管理的目的。同时引用计数还可以指使用引用计数技术回收未使用资源的垃圾回收算法。
WebRTC中实现了跨平台(Windows、MacOS、Linux、iOS、Android)的线程类: rtc::Thread,
代码位于 rtc_base/ 目录下的 thread.h 和 thread.cc 中。
对WebRTC的线程模型可以从创建和运行两个不同的角度来理解。
一、从创建的角度,WebRTC的线程被划分未两层:
(1)第一层由三个线程构成:网络线程、工作线程、信令线程
这三个线程可以在WebRTC库外由用户自己创建后传给WebRTC管理,也可以由WebRTC在构造PeerConnectionFactory对象时自己创建。一般情况下使用后一种方法,即让WebRTC自己创建三个线程。实际上,当WebRTC自己创建线程时,其实只创建了两个线程—网络线程 和 工作线程,而信令线程则由主线程来代替。
实现代码:
// src/pc/peer_connection_factory.cc
PeerConnectionFactory::PeerConnectionFactory() {
if (!network_thread_) {
owned_network_thread_ = rtc::Thread::CreateWithSocketServer();
owned_network_thread_->SetName("pc_network_thread", nullptr);
owned_network_thread_->Start();
network_thread_ = owned_network_thread_.get();
}
if (!worker_thread_) {
wned_worker_thread_ = rtc::Thread::Create();
owned_worker_thread_->SetName("pc_worker_thread", nullptr);
owned_worker_thread_->Start();
worker_thread_ = owned_worker_thread_.get();
}
// 信令线程指向当前线程,即主线程:
if (!signaling_thread_) {
signaling_thread_ = rtc::Thread::Current();
if (!signaling_thread_) {
// If this thread isn't already wrapped by an rtc::Thread, create a
// wrapper and own it in this class.
signaling_thread_ = rtc::ThreadManager::Instance()->WrapCurrentThread();
wraps_current_thread_ = true;
}
}
(2)第二层子线程,由第一层的三个主线程创建:
在 网络线程、工作线程、信令线程 创建好之后,它们会根据业务需要创建子线程,从而形成第二层线程。
其中,工作线程会创建 视频编码线程、视频解码线程、音频编码线程、Pacer线程等;
信令线程会创建视频采集线程、视频渲染线程、音频采集线程、音频渲染线程等。
WebRTC线程模型中的第一层线程是必须存在的,也就是说只要使用WebRTC库,三个线程就必然会创建;第二层线程则不一定存在,比如使用WebRTC时如果没有开启音视频业务,就不会创建与音视频相关的线程。
二、从运行的角度看,WebRTC的线程模型分为三层:
第一层还是网络线程、工作线程、信令线程;
第二层是音视频编解码层;
第三层是硬件访问层,用于进行音视频的采集和渲染。
数据会在WebRTC的线程之间不断地切换(传递),为了实现这种数据的传递,每个线程对象都是由 线程本身 和 队列 组成。
队列中可以存放任何类型的数据,既可以是音视频这种二进制数据,也可以是一个任务对象。
对于接收线程来说,它通过一个while循环不停地从自己的队列中取出数据进行处理。
例如图中,当线程一将处理好的数据交换到线程二进行处理时,它只需将自己输出的数据/任务 插入到线程二的队列中即可。
WebRTC提供了两种线程间切换的方法: Post() 和 Send()。
Post():异步的,构造一个Message对象,将其插入到接收方线程的队列中即可;
Send():同步的,将构造的Message插入到线程队列之后,需要等待接收方完成任务之后,发送方才能继续自己的处理逻辑。
在Post()和Send()方法的基础上,WebRTC还提供了两种变型的方法用于线程切换:PostTask()、Invoke()。
定义:
// rtc_base/thread.h
// Post:
virtual void Post(const Location& posted_from, // 记录发生线程切换的位置,该参数对于排查问题有帮忙,可以快速定位到问题所在的线程,除此之外它不参与任何逻辑处理
MessageHandler* phandler, // phandler是一个包含OnMessage()方法的类对象,当线程发生切换时,目标线程会调用该参数的OnMessage()方法做进一步的逻辑处理(可以简单理解为传给目标线程的任务回调函数)
uint32_t id = 0, // 表示消息id,当源线程处理的数据包含多种类型(例如音频、视频)时,目标线程可以通过该参数对数据进行区分
MessageData* pdata = nullptr, // 数据指针,指向目标线程需要处理的数据地址
bool time_sensitive = false); // 此参数以被淘汰不使用,一直为flase
使用Post()方法:
...
signaling_thread()->Post(RTC_FROM_HERE, // 宏,用于获取当前的位置信息,实际上就是在其内部生成一个带有函数名和行号的Location对象
this, // this指针,即指向调用Post方法的对象,注意该对象必须是继承自 MessageHandler类的,且实现了OnMessage()方法
MSG_FIRSTPACKETRECEIVED); // 消息ID,指明是什么信息
Post()方法的实现:
Post()方法的实现非常简单,就是将输入的参数填入到Message对象中,然后将其插入到目标线程的message_队列中。
//
void Thread::Post(...) {
CritScop cs(&crit_);
Message msg;
msg.posted_from = posted_from;
msg.phandler = phandler;
msg.message_id = id;
msg.pdata = pdata;
messages_.push_back(msg);
...
}
消息接收方(目标线程)的处理:
当目标线程启动时,它会执行PreRun()方法,PreRun()方法又会调用Run()方法,而Run()方法最终调用ProcessMessage()方法实现消息的分发与处理,调用过程如下:
启动线程(Thread)->PreRun()->Run()->ProcessMessage()
PorcessMessage()方法的实现:
bool Thread::ProcessMessage(int cmsLoop) {
...
while (true) {
Message msg;
if (!Get(&msg, cmsNext)) // 在while死循环中不断调用Get()方法,从线程的message_队列中获取消息
return !IsQuiting();
Dispatch(&msg); // 处理从队列中获取到的消息
...
}
...
}
void Thread::Dispatch(Message* pmsg) {
...
pmsg->phandler->OnMessage(pmsg); // 调用Message对象中phandler对象的OnMessage()方法,最终完成消息的处理
...
}
PostTask()方法是从Post()方法衍生出来的,其整体逻辑与Post()方法是一致的。
PostTask()方法的实现:
PostTask()方法中也是调用了Post()方法来实现的,它将Post的第三个参数写死(id=0),只允许改变第四个输入参数。
void Thread::PostTask(... task) {
Post(RTC_FROM_HERE,
&queued_task_handler_,
0, // id
new ...(std::move(task)));
}
OnMessage()方法的实现:
void QueuedTaskHandler::OnMessage(Message* msg) {
...
auto* data = static_cast<...>(msg->pdata);
std::unique_ptr<..>task = std::move(data->data());
...
if (!task->Run())
...
}
线程: