文章目录
WebRtc中的线程设计非常具有参考价值,通过学习它们的实现,可以对线程的理解更上一个台阶。
在WebRtc并不是简单的对os 线程相关的API进行封装,而是结合功能实现需求对线程进行了设计。
在webrtc有两种线程对象:
- rtc::Thread 对象
rtc::Thread
是webrtc中最重要的线程类型。它与WebRtc库的实现深度结合。
- rtc::ProcessThread 对象
rtc::ProcessThread
线程类型,主要是用作执行一些定时任务。
这篇文章将介绍rtc::Thread及它在WebRtc中的使用。
rtc:Thread
线程模型
rtc::Thread
的线程模型如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GZPu3OiZ-1678665809251)(https://note.youdao.com/yws/api/personal/file/WEBff381d87c74c6937c35f05c025579f26?method=download&shareKey=d2fd984d0ab7f17fd5395027c1aa3fab#clientId=ua4f980d8-c6d9-4&id=nodDw&originHeight=1190&originWidth=1574&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u1c3d1033-3e4f-4638-8ea8-2f6a65535ea&title=)]
如上图所示,rtc::Thread
不仅仅是只产生一个线程,它还包含一个队列**(message list)**,将它称为异步任务队列更适合。
它的基本功能就是在线程中执行任务队列中的任务。
它靠事件驱动,也就是说当message list
中没有任务(message
)时,它会阻塞在事件等待上,当有事件唤醒时后会继续在队列中去任务执行。
主要成员及方法
类图
类图中列出了rtc::Thread
的核心类及方法。
Thread
类继承自TaskQueueBase
,从语意就表明了它是一个任务队列。
核心成员变量:
MessageList messages_
就是任务队列,它是一个std::list
,定义如下:
typedef std::list<Message> MessageList
,Message
类就任务的抽象。
PriorityQueue delayed_message_
延迟任务队列,是一个优先队列。
SocketServer* const ss_
事件驱动器,从名字上就可以看出它可以处理网络IO事件。
核心方法:
它的核心方法就是ProcessMessage
和Get
方法。
ProcessMessage
是线程的执行体,是一个while(true)
循环,它不断的从Get
中取出任务然后执行,方法框架如下:
bool Thread::ProcessMessages(int cmsLoop) {
while (true) {
Message msg;
//取出任务
Get(&msg, cmsNext);
...
//执行任务
Dispatch(&msg);
}
}
Get
方法,首先取出队列中的任务,如果没有任务则处理事件,如果没有事件则阻塞到Wait
方法上。
bool Thread::Get(Message* pmsg, int cmsWait, bool process_io) {
//取出队列中的任务
while(true) {
...
if (messages_.empty()) {
break;
} else {
*pmsg = messages_.front();
messages_.pop_front();
}
...
return true;
}
...
//如果没有任务则执行事件,如何也没有事件就阻塞在Wait方法上
{
// Wait and multiplex in the meantime
if (!ss_->Wait(static_cast<int>(cmsNext), process_io))
return false;
}
}
控制类方法
bool Start()
void Stop()
void Run()
这三个是典型的线程控制方法。
任务类方法
异步方法
它是异步队列,有post
系列方法,用于将任务投递到线程中执行,这些post
方法都是异步方法,马上返回。
- 放入一个
QueuedTask
类型的任务。QueuedTask
方法内部被封装成了Message
类型。
void PostTask(std::unique_ptr<webrtc::QueuedTask> task) override;
- 放入一个
QueuedTask
类型的任务,在delay ms后执行。
void PostDelayedTask(std::unique_ptr<webrtc::QueuedTask> task,
uint32_t milliseconds) override;
- 将任务投递到线程中执行。
virtual void Post(const Location& posted_from,
MessageHandler* phandler,
uint32_t id = 0,
MessageData* pdata = nullptr,
bool time_sensitive = false);
- 将任务投递到线程中,在delay ms后执行。
virtual void PostDelayed(const Location& posted_from,
int delay_ms,
MessageHandler* phandler,
uint32_t id = 0,
MessageData* pdata = nullptr);
- 将任务投递到线程中,在指定时间点执行。
virtual void PostAt(const Location& posted_from,
int64_t run_at_ms,
MessageHandler* phandler,
uint32_t id = 0,
MessageData* pdata = nullptr);
下面两个是基于上面三个Post
方法的封装,它们更易用。可传入任意类型的可调用对象(函数对象,lambda,普通方法)。
template <class FunctorT>
void PostTask(const Location& posted_from, FunctorT&& functor);
template <class FunctorT>
void PostDelayedTask(const Location& posted_from,
FunctorT&& functor,
uint32_t milliseconds)
利用这些**Post**
方法可以很方便的在**rtc::Thread**
间进行任务投递,也就是说可以在一个**rtc::Thread**
中向另外一个**rtc:Thread**
投递任务。
同步方法
send
方法向线程队列中投递一个任务,等任务执行完后才返回。
virtual void Send(const Location& posted_from,
MessageHandler* phandler,
uint32_t id = 0,
MessageData* pdata = nullptr);
下面两个Invoke
方法是对send
方法的封装,可以传入可调用对象。并且可以获得任务的执行结果。
template <
class ReturnT,
typename = typename std::enable_if<!std::is_void<ReturnT>::value>::type>
ReturnT Invoke(const Location& posted_from, FunctionView<ReturnT()> functor);
template <
class ReturnT,
typename = typename std::enable_if<std::is_void<ReturnT>::value>::type>
void Invoke(const Location& posted_from, FunctionView<void()> functor)
构造类方法
从上面的类可以看到创建rtc::Thread
对象有多种方式,可以直接使用它的构造函数,它们都要求传入一个事件驱动器(SocketServer
)。也可以使用create
系统方法,相比构造函数更简单些。
static std::unique_ptr<Thread> CreateWithSocketServer();
前面提到thread的任务队列是靠事件驱动,这个方法就是创建一个可以处理IO事件的thread。
static std::unique_ptr<Thread> Create();
该方法是创建一个不处理IO事件,靠事件驱动的thread。
static Thread* Current();
将当前线程包装为一个Thread
对象,注意如果调用该方法产生Thread
对象,后不能再调用Start
方法。因为线程已经运行了,无需再调用。可以像如下使用方法,来利用它的队列:
Thread* th_main = Thread::Current();
th_main->ProcessMessages(kForever);
rtc::Thread的用途
**rtc::Thread**
它有三个用途:
- 作为一个普通的
**Thread**
封装类来使用(需要重载Run()方法)。 - 作为一个消息队列来使用(此时创建的SocketServer对象是NullSocketServer,NullSocketServer的作用是让线程为事件驱动,而不是轮询驱动)。
- 作为一个具有管理IO功能的消息队列来使用(此时创建的SocketServer对象是PhysicalSocketServer,可以处理I/O事件)。
WebRtc中的三大线程
WebRtc中的线程模型非常清晰,按功能划分为三大线程,各司其责。
- 信令线程
**signaling_thread**
,调用WebRtc API的线程(就是客户程序线程)。 - 工作线程
**worker_thread**
,内部常规逻辑工作的线程。 - 网络线程
**network_thread**
,数据收发线程。
它们都是rtc::Thread
对象。创建PreeConnection对象时,就要求传入这三个线程对象。
RTC_EXPORT rtc::scoped_refptr<PeerConnectionFactoryInterface>
CreatePeerConnectionFactory(
rtc::Thread* network_thread,
rtc::Thread* worker_thread,
rtc::Thread* signaling_thread,
rtc::scoped_refptr<AudioDeviceModule> default_adm,
rtc::scoped_refptr<AudioEncoderFactory> audio_encoder_factory,
rtc::scoped_refptr<AudioDecoderFactory> audio_decoder_factory,
std::unique_ptr<VideoEncoderFactory> video_encoder_factory,
std::unique_ptr<VideoDecoderFactory> video_decoder_factory,
rtc::scoped_refptr<AudioMixer> audio_mixer,
rtc::scoped_refptr<AudioProcessing> audio_processing);
线程指针,可以传入NULL,由WebRtc内部创建。
network_thread 是一个SocketServer
owned_network_thread_ = rtc::Thread::CreateWithSocketServer();
owned_network_thread_->SetName("pc_network_thread", nullptr);
network_thread顾名思义,用来收发包。如下是network_thread的收包堆栈。
network_queue发包堆栈
worker_thread 是一个消息队列
owned_worker_thread_ = rtc::Thread::Create(); //创建的是NullSocketServer
owned_worker_thread_->SetName("pc_worker_thread", nullptr);
signaling_thread 也是一个消息队列
在WebRtc内部,如果signaling_thread
为NULL,会将调用API的线程包装成一个rtc::thread
。
三大线程利用**rtc::Thread**
的post方法,相互投递任务,进行线程切换,责任划分就非常清晰。
rtc::Thread
的用法
创建一个没有网络IO功能的rtc::Thread
- 定义一个任务,它是一个函数对象。
//一个任务
struct ThreadTask
{
void operator()()
{
std::cout<<std::this_thread::get_id()<<std::endl;
}
};
- 创建线程
auto p1 = Thread::Create();
p1->Start();
- 向
p1
投递一个ThreadTask
p1->PostTask(RTC_FROM_HERE,ThreadTest());
- 向
p1
投递一个延迟执行的ThreadTask
//延迟1秒执行
p1->PostDelayedTask(RTC_FROM_HERE,ThreadTest(),1000);
创建一个有IO功能的rtc::Thread
std::unique_ptr<Thread> th1 = Thread::CreateWithSocketServer()
rtc::Thread
单元测试中例子
了解rtc::Thread
更多的使用方法,可以看看它的单元测试代码,下面列举PostTask
接口的例子,可以向PostTask
中传入各式可调用对象。
InvokesWitBind
示例
TEST(ThreadPostTaskTest, InvokesWithBind) {
std::unique_ptr<rtc::Thread> background_thread(rtc::Thread::Create());
background_thread->Start();
Event event;
background_thread->PostTask(RTC_FROM_HERE, Bind(&Event::Set, &event));
event.Wait(Event::kForever);
}
InvokesWithLambda
示例
TEST(ThreadPostTaskTest, InvokesWithLambda) {
std::unique_ptr<rtc::Thread> background_thread(rtc::Thread::Create());
background_thread->Start();
Event event;
background_thread->PostTask(RTC_FROM_HERE, [&event] {
event.Set(); });
event.Wait(Event::kForever);
}
InvokesWithCopiedFunctor
示例
TEST(ThreadPostTaskTest, InvokesWithCopiedFunctor) {
std::unique_ptr<rtc::Thread> background_thread(rtc::Thread::Create());
background_thread->Start();
LifeCycleFunctor::Stats stats;
Event event;
//LifeCycleFunctor是有拷贝语义的对象
LifeCycleFunctor functor(&stats, &event);
background_thread->PostTask(RTC_FROM_HERE, functor);
event.Wait(Event::kForever);
EXPECT_EQ(1u, stats.copy_count);
EXPECT_EQ(0u, stats.move_count);
}
InvokesWithMovedFunctor
示例
TEST(ThreadPostTaskTest, InvokesWithMovedFunctor) {
std::unique_ptr<rtc::Thread> background_thread(rtc::Thread::Create());
background_thread->Start();
LifeCycleFunctor::Stats stats;
Event event;
LifeCycleFunctor functor(&stats, &event);
//通过std::move产生右值引用
background_thread->PostTask(RTC_FROM_HERE, std::move(functor));
event.Wait(Event::kForever);
EXPECT_EQ(0u, stats.copy_count);
EXPECT_EQ(1u, stats.move_count);
}
InvokesWithReferencedFunctorShouldCopy
示例
TEST(ThreadPostTaskTest, InvokesWithReferencedFunctorShouldCopy) {
std::unique_ptr<rtc::Thread> background_thread(rtc::Thread::Create());
background_thread->Start();
LifeCycleFunctor::Stats stats;
Event event;
LifeCycleFunctor functor(&stats, &event);
//传入对象引用
LifeCycleFunctor& functor_ref = functor;
background_thread->PostTask(RTC_FROM_HERE, functor_ref);
event.Wait(Event::kForever);
EXPECT_EQ(1u, stats.copy_count);
EXPECT_EQ(0u, stats.move_count);
}