WebRTC线程介绍

一、引言:

众所周知,要想充分利用现代CPU的资源,操作系统引入了多进程、多线程等手段。但是,多线程的管理又是一个令人头大的问题;

问:有没有一个执行效率高、线程安全、易用性高的线程管理模块呢?

答:Google又一次帮我们做好了,而且,尽管开源了,还是特别的复杂,搞不懂这部分知识,阁下休想读懂WebRTC源代码!!!

注意:笔者会把干扰代码都删除,只贴关键代码,这个也是阅读开源代码非常重要的办法!

本章思维导图:

image.png

二、线程模型:

1、常见线程模型:

  • 所有任务都进入消息队列,然后,如果有空闲线程就从队列取出一个任务让处理,如果没有就等待:
    image.png
*   任务队列中包含了很多个对象(对象都有run方法);

*   可以控制线程数量,因为线程切换需要在用户态和内核态切换,开销比较大(当然现代语言,比如go语言就只在用户态切换);

*   使用任务队列,保证任务丢失不了;

*   减少了线程的创建和销毁带来的开销;

*   减少了锁的使用;
  • 每个线程都有一个自己的消息队列,需要哪个线程处理任务,将任务放入其队列即可:(WebRTC便使用的这种)
    image.png
*   队列中有事件的时候,可以唤醒睡眠的线程;

*   减少了锁的使用;

*   需要让哪个线程处理,就往队列丢一个任务即可;

2、WebRTC三大线程:

WebRTC中有三大线程:工作线程、信令线程、网络线程;

  • 工作线程: 工作在媒体引擎层,主要负责业务逻辑处理,比如音视频编解码、采集、渲染这种耗时的操作;

  • 信令线程: 工作在PeerConnection层,负责和用户的一些交互,比如创建offer、answer、candidate等等;WebRTC为了让绝大部分API都运行在信令线程,还专门做了一层Proxy层,强制将API的调用分配到信令线程

  • 网络线程: 工作在传输层,网络相关操作都在此,比如,从网络接收数据并转给工作线程,或者从工作线程接收数据,发送到网络;

三、线程管理:

1、线程类:

注意,我们带着问题阅读代码,前面说了webrtc的线程模型,一个线程对应一个“消息队列”,想2秒钟,这个机制怎么运行呢?

自问自答:就是线程发现队列中有消息了就获取消息去处理消息。这就有两个问题,第一个是怎么获取,第二个是怎么处理,再想2秒钟呢?

再次自问自答:任务处理待会儿讲,消息获取有两种方式,一种是不停轮询(像select机制),浪费资源;一种是队列有消息事件触发线程去处理(类似epoll)非常不错;

结论:上面不光有线程、还有消息队列、还有一种获取处理机制。多么内聚的一个需求啊,因此,WebRTC将这些线程相关的七大姑八大姨全都封装成了一个Thread类;

Thread类代码:

(枝枝蔓蔓已经删除,只留下重点代码)

<pre class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" spellcheck="false" lang="cpp" cid="n346" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: pre; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; position: relative !important;"> // 代码路径:.\rtc_base\thread.h
class RTC_LOCKABLE RTC_EXPORT Thread : public webrtc::TaskQueueBase {
public:
// 构造函数
Thread(SocketServer* ss, bool do_init);
Thread(std::unique_ptr<SocketServer> ss, bool do_init);
// 静态方法创建socket
static std::unique_ptr<Thread> CreateWithSocketServer();
static std::unique_ptr<Thread> Create();
// 代码任意位置获取当前线程的Thread对象
static Thread* Current();

// 线程控制方法
virtual void Quit();
virtual bool IsQuitting();
virtual void Restart();
// 从队列获取消息
virtual bool Get(Message* pmsg,
int cmsWait = kForever,
bool process_io = true);
virtual bool Peek(Message* pmsg, int cmsWait = 0);
// 向队列添加消息
virtual void Post(const Location& posted_from,
MessageHandler* phandler,
uint32_t id = 0,
MessageData* pdata = nullptr,
bool time_sensitive = false);
virtual void PostDelayed(const Location& posted_from,
int delay_ms,
MessageHandler* phandler,
uint32_t id = 0,
MessageData* pdata = nullptr);
void PostTask(std::unique_ptr<webrtc::QueuedTask> task) override;
// 分发消息
virtual void Dispatch(Message* pmsg);
bool Start();
virtual void Stop();
virtual void Run();

// 线程对应的消息队列,要想让线程处理,就只能放这里面
MessageList messages_ RTC_GUARDED_BY(crit_);
// 延迟队列,不太重要你就放到这儿,待会儿帮你处理
PriorityQueue delayed_messages_ RTC_GUARDED_BY(crit_);

// 临界区,看看上面队列的定义后面都跟了一个RTC_GUARDED_BY(crit_),
// 字面意思很明显了,相当于一把加在队列上面的锁(Guard),要想读写队列,先获取crit_
RecursiveCriticalSection crit_;

// 其实就是一个普通的事件处理类
SocketServer* const ss_;

// 真正处理数据的线程
HANDLE thread_ = nullptr;
};</pre>

注意:

  • Thread类的数据主要是线程对象thread_、两种队列、用来获取事件的SocketServer对象;

  • Thread类的方法主要有三大类:队列数据读写、线程切换、线程控制;

有了Thread类我们就可以愉快地、放肆地创建线程(对象)了?且慢,你打算怎么创建呢?创建了之后怎么管理呢?我们下一个主角就登场了,他就是为管理Thread而生的,因此叫做ThreadManager;

2、线程管理类:

ThreadManager代码:

<pre class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" spellcheck="false" lang="cpp" cid="n356" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: pre; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; position: relative !important;"> // 代码路径:.\rtc_base\thread.h
class RTC_EXPORT ThreadManager {
public:
// 单例模式创建的对象
static ThreadManager* Instance();
// 增删管理的Thread对象
static void Add(Thread* message_queue);
static void Remove(Thread* message_queue);
static void Clear(MessageHandler* handler);

// 根据Tls管理Tread对象
void SetCurrentThread(Thread* thread);

private:
ThreadManager();
~ThreadManager();

// 队列管理所有Thread对象
std::vector<Thread*> message_queues_ RTC_GUARDED_BY(crit_);

// 临界区
RecursiveCriticalSection crit_;
size_t processing_ RTC_GUARDED_BY(crit_) = 0;

// 使用TLS管理线程
const DWORD key_;
};</pre>

TLS介绍:

这里面这个key有讲究,使用了TLS(线程局部变量)去管理,你可以理解成在系统底层为每个变量创建了一个键值对,存为全局变量,为了方便快速找到线程(这样容易理解了,但是并不严谨,想严谨请看https://zh.wikipedia.org/wiki/%E7%BA%BF%E7%A8%8B%E5%B1%80%E9%83%A8%E5%AD%98%E5%82%A8);

windows平台相关api如下:

  • TlsAlloc:为Tls对象分配空间,并且返回对应index;

    • <pre class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" spellcheck="false" lang="cpp" cid="n365" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: pre; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 0px; width: inherit; position: relative !important;"> ThreadManager::ThreadManager()
      : key_(TlsAlloc()), main_thread_ref_(CurrentThreadRef()) {}</pre>
  • TlsSetValue:插入一个key-value,其中key就是tid,value就是我们上面所说的Thread对象了

    • <pre class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" spellcheck="false" lang="cpp" cid="n370" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: pre; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 0px; width: inherit; position: relative !important;"> // 函数调用顺序ThreadManager::SetCurrentThread->ThreadManager::SetCurrentThreadInternal
      void ThreadManager::SetCurrentThreadInternal(Thread* thread) {
      TlsSetValue(key_, thread);
      }</pre>
  • TlsGetValue:获取当前线程tid对应的Thread对象

    • <pre class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" spellcheck="false" lang="cpp" cid="n375" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: pre; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 0px; width: inherit; position: relative !important;"> Thread* ThreadManager::CurrentThread() {
      return static_cast<Thread*>(TlsGetValue(key_));
      }</pre>

3、线程的创建:

这个直接上代码(无关代码已经删除):

  • main函数中创建了Thread对象,并且使用ThreadManager进行管理,而ThreadManager中会调用TlsSetValue将对象用Tls管理起来;

<pre class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" spellcheck="false" lang="cpp" cid="n381" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: pre; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; position: relative !important;"> int PASCAL wWinMain(HINSTANCE instance,
HINSTANCE prev_instance,
wchar_t* cmd_line,
int cmd_show) {
rtc::Win32SocketServer w32_ss;
// 创建线程对象
rtc::Win32Thread w32_thread(&w32_ss);
// 线程对象加入ThreadManager进行管理
rtc::ThreadManager::Instance()->SetCurrentThread(&w32_thread);
return 0;
}

void ThreadManager::SetCurrentThread(Thread* thread) {
SetCurrentThreadInternal(thread);
}

void ThreadManager::SetCurrentThreadInternal(Thread* thread) {
TlsSetValue(key_, thread);
}</pre>

  • Thread对象会在构造函数中就将自己插入到ThreadManager中:

<pre class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" spellcheck="false" lang="cpp" cid="n385" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: pre; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; position: relative !important;"> Thread::Thread(SocketServer* ss, bool do_init)
: fPeekKeep_(false),
delayed_next_num_(0),
fInitialized_(false),
fDestroyed_(false),
stop_(0),
ss_(ss) {
RTC_DCHECK(ss);
ss_->SetMessageQueue(this);
SetName("Thread", this); // default name
if (do_init) {
// 看这儿,是关键
DoInit();
}
}

void Thread::DoInit() {
if (fInitialized_) {
return;
}

fInitialized_ = true;
// 将自己添加到了ThreadManager中去
ThreadManager::Add(this);
}

// static
void ThreadManager::Add(Thread* message_queue) {
return Instance()->AddInternal(message_queue);
}

void ThreadManager::AddInternal(Thread* message_queue) {
CritScope cs(&crit_);
// Prevent changes while the list of message queues is processed.
RTC_DCHECK_EQ(processing_, 0);
// 最终放到了成员变量message_queues_
message_queues_.push_back(message_queue);
}</pre>

看注释应该已经很明白了吧?

4、三大线程的创建和启动:

我们就看下三大线程都是啥时候创建,啥时候启动的 看下代码,看这几个线程是什么时候创建的:

备注:我们从peer_connection_client点击连接之后开始分析,此时首先应该执行Conductor::ConnectToPeer

<pre class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" spellcheck="false" lang="cpp" cid="n390" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: pre; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; position: relative !important;"> // 代码路径:.\examples\peerconnection\client\conductor.cc
void Conductor::ConnectToPeer(int peer_id) {
// 无关代码已经删除...

// 初始化PeerConnection
if (InitializePeerConnection()) {
peer_id_ = peer_id;
peer_connection_->CreateOffer(
this, webrtc::PeerConnectionInterface::RTCOfferAnswerOptions());
} else {
main_wnd_->MessageBox("Error", "Failed to initialize PeerConnection", true);
}
}

// 这个函数非常重要
bool Conductor::InitializePeerConnection() {
// 无关代码已经删除...
// 这函数前四个参数如果你想使用自己定义的线程函数,那么就传入,否则就使用的是webrtc内部的默认函数
peer_connection_factory_ = webrtc::CreatePeerConnectionFactory(
nullptr /* network_thread /, nullptr / worker_thread /,
nullptr /
signaling_thread /, nullptr / default_adm /,
webrtc::CreateBuiltinAudioEncoderFactory(),
webrtc::CreateBuiltinAudioDecoderFactory(),
webrtc::CreateBuiltinVideoEncoderFactory(),
webrtc::CreateBuiltinVideoDecoderFactory(), nullptr /
audio_mixer /,
nullptr /
audio_processing /);
// 无关代码已经删除...
if (!CreatePeerConnection(/
dtls=*/true)) {
main_wnd_->MessageBox("Error", "CreatePeerConnection failed", true);
DeletePeerConnection();
}
// 添加track到PeerConnection中
AddTracks();
return peer_connection_ != nullptr;
}
// 接下来就跳出examples,进入webrtc源代码了

// 代码路径:.\api\create_peerconnection_factory.cc
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,
AudioFrameProcessor* audio_frame_processor) {
// 具体参数都打包,为了方便向下传递
PeerConnectionFactoryDependencies dependencies;
dependencies.network_thread = network_thread;
// 中间就是一大堆往dependencies填充数据的,类似于上面这一行,可以省略;

// 开始创建Factory
return CreateModularPeerConnectionFactory(std::move(dependencies));
}

// 代码路径:.\pc\peer_connection_factory.cc
rtc::scoped_refptr<PeerConnectionFactoryInterface>
CreateModularPeerConnectionFactory(PeerConnectionFactoryDependencies dependencies) {
// 无关代码已经删除..
// 创建PeerConnectionFactory对象
auto pc_factory = PeerConnectionFactory::Create(std::move(dependencies));
if (!pc_factory) {
return nullptr;
}

// 创建PeerConnectionFactory代理类(非常复杂,后面分析)
return PeerConnectionFactoryProxy::Create(pc_factory->signaling_thread(),
pc_factory);
}

// 至此,PeerConnectionFactory便已经创建好了,我们再看下如果是外部传入的线程函数,怎么处理的

// 代码路径:.\pc\peer_connection_factory.cc
// Static
rtc::scoped_refptr<PeerConnectionFactory> PeerConnectionFactory::Create(
PeerConnectionFactoryDependencies dependencies) {
// 根据dependencies创建一个连接上下文类
auto context = ConnectionContext::Create(&dependencies);
if (!context) {
return nullptr;
}
return new rtc::RefCountedObject<PeerConnectionFactory>(context,
&dependencies);
}

// 代码路径:.\pc\connection_context.cc
// Static
rtc::scoped_refptr<ConnectionContext> ConnectionContext::Create(
PeerConnectionFactoryDependencies* dependencies) {
// 构造对象
auto context = new rtc::RefCountedObject<ConnectionContext>(dependencies);
// 无关代码已删除
return context;
}
// 上面这里面构造了一个context,因此,我们看看构造函数干了什么

// 通过MabeStartThread函数初始化了工作者、网络线程,信令线程比较特殊一点,
// 是由于信令线程可以直接托管进程中的主线程(准确来说应该是当前调用线程),所以调用的函数是MaybeWrapThread
ConnectionContext::ConnectionContext(PeerConnectionFactoryDependencies* dependencies)
: network_thread_(MaybeStartThread(dependencies->network_thread,
"pc_network_thread",
true,
owned_network_thread_)),
worker_thread_(MaybeStartThread(dependencies->worker_thread,
"pc_worker_thread",
false,
owned_worker_thread_)),
signaling_thread_(MaybeWrapThread(dependencies->signaling_thread,
wraps_current_thread_)) {
// 无关代码已经删除
}

rtc::Thread* MaybeStartThread(rtc::Thread* old_thread,
const std::string& thread_name,
bool with_socket_server,
std::unique_ptr<rtc::Thread>& thread_holder) {
// 已经传入了,我就不做任何事
if (old_thread) {
return old_thread;
}
if (with_socket_server) {
// 需要使用socket事件的
thread_holder = rtc::Thread::CreateWithSocketServer();
} else {
// 不关心socket事件
thread_holder = rtc::Thread::Create();
}
thread_holder->SetName(thread_name, nullptr);
// 创建线程并返回
thread_holder->Start();
return thread_holder.get();
}</pre>

前面线程已经Start,我们看下怎么收发Message的(同样是删除不相关代码):

<pre class="md-fences mock-cm md-end-block md-fences-with-lineno" spellcheck="false" lang="cpp" cid="n393" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: pre; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 8px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; position: relative !important;">// 代码路径:.\rtc_base\thread.cc
bool Thread::Start() {
ThreadManager::Instance();
// 创建新的线程,新线程执行PreRun方法
thread_ = CreateThread(nullptr, 0, PreRun, this, 0, &thread_id_);
if (!thread_) {
return false;
}
return true;
}

/**

  • 新线程执行的代码
    /
    DWORD WINAPI Thread::PreRun(LPVOID pv) {
    Thread
    thread = static_cast<Thread*>(pv);
    ThreadManager::Instance()->SetCurrentThread(thread);
    // 开始运行
    thread->Run();

ThreadManager::Instance()->SetCurrentThread(nullptr);
return 0;
} // namespace rtc

void Thread::Run() {
ProcessMessages(kForever);
}

/**

  • 处理消息队列里面的消息
    */
    bool Thread::ProcessMessages(int cmsLoop) {
    while (true) {
    Message msg;
    // 从自己队列获取一个Message
    if (!Get(&msg, cmsNext))
    return !IsQuitting();
    // 分发这个Message
    Dispatch(&msg);
    }
    }</pre>

总结:

上面一大堆代码,您可能已经看累了,我也写个半死,其实大体思路就是: 1、我传入线程你就用我传入的,否则用内部默认的; 2、内部默认的又根据要不要处理socket事件分别创建不同类型的线程,比如网络线程那肯定需要啊,信令线程可能就不需要; 3、发现工作线程和网络线程是用了MaybeStartThread,而信令线程使用的是MaybeWrapThread,这个Wrap也就是说把当前线程当作信令线程包装起来,不用重新创建; 4、线程创建完成就直接调用Start启动了,这个也是很多语言和框架的做法,比如java的线程就是继承Thread类,然后override其run方法,然后调用start方法线程就启动了; QT中也是类似继承QThread,balabala。。。

四、线程调度:

1、目的:

前面已经创建了线程,并且已经Run起来了,可以Get到Message,并且DisPatch出去了,我们本章就进这几个函数里头看看,顺便看下线程之间怎么发送消息的;

2、收发消息:

接着前面Thread::ProcessMessages继续往下看;

Get:

注意这儿收消息需要从两个队列收,普通消息队列和延迟消息队列;

<pre class="md-fences mock-cm md-end-block md-fences-with-lineno" spellcheck="false" lang="CPP" cid="n403" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: pre; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 8px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; position: relative !important;">bool Thread::Get(Message* pmsg, int cmsWait, bool process_io) {
// Return and clear peek if present
// Always return the peek if it exists so there is Peek/Get symmetry

if (fPeekKeep_) {
*pmsg = msgPeek_;
fPeekKeep_ = false;
return true;
}

// Get w/wait + timer scan / dispatch + socket / event multiplexer dispatch

int64_t cmsTotal = cmsWait;
int64_t cmsElapsed = 0;
int64_t msStart = TimeMillis();
int64_t msCurrent = msStart;
while (true) {
// Check for posted events
int64_t cmsDelayNext = kForever;
bool first_pass = true;
while (true) {
// All queue operations need to be locked, but nothing else in this loop
// (specifically handling disposed message) can happen inside the crit.
// Otherwise, disposed MessageHandlers will cause deadlocks.
{
CritScope cs(&crit_);
// On the first pass, check for delayed messages that have been
// triggered and calculate the next trigger time.
if (first_pass) {
first_pass = false;
// 看看延迟队列有没有数据要发送(这是个优先级队列,堆顶的就是优先级最高的)
while (!delayed_messages_.empty()) {
if (msCurrent < delayed_messages_.top().run_time_ms_) {
cmsDelayNext =
TimeDiff(delayed_messages_.top().run_time_ms_, msCurrent);
break;
}
// 达到发送时间的,就丢给普通消息队列去处理
messages_.push_back(delayed_messages_.top().msg_);
delayed_messages_.pop();
}
}
// 从消息队列队首拿出一个Message到*pmsg
if (messages_.empty()) {
break;
} else {
*pmsg = messages_.front();
messages_.pop_front();
}
} // crit_ is released here.

  // If this was a dispose message, delete it and skip it.
  if (MQID_DISPOSE == pmsg->message_id) {
    RTC_DCHECK(nullptr == pmsg->phandler);
    delete pmsg->pdata;
    *pmsg = Message();
    continue;
  }
  return true;
}

if (IsQuitting())
  break;

// Which is shorter, the delay wait or the asked wait?

int64_t cmsNext;
if (cmsWait == kForever) {
  cmsNext = cmsDelayNext;
} else {
  cmsNext = std::max<int64_t>(0, cmsTotal - cmsElapsed);
  if ((cmsDelayNext != kForever) && (cmsDelayNext < cmsNext))
    cmsNext = cmsDelayNext;
}

{
  // Wait and multiplex in the meantime
  // 一直Wait,直到有消息的话SocketServer就会发送事件触发WakeUp继续往下走(那么就会重新进入while循环)
  if (!ss_->Wait(static_cast<int>(cmsNext), process_io))
    return false;
}

// 超时就退出
msCurrent = TimeMillis();
cmsElapsed = TimeDiff(msCurrent, msStart);
if (cmsWait != kForever) {
  if (cmsElapsed >= cmsWait)
    return false;
}

}
return false;
}</pre>

注意:这个wait和wakeup机制你应该要非常清楚才对,不懂可以去看看C++的”条件变量(conditional variable)“,巩固下基础;

Message:

前面的Get函数执行完毕,就会获得一个Message 并且放在Get的出参返回;然后交给Dispath去投递,我们先看看Message:

<pre class="md-fences mock-cm md-end-block md-fences-with-lineno" spellcheck="false" lang="cpp" cid="n408" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: pre; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 8px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; position: relative !important;">struct Message {
Message() : phandler(nullptr), message_id(0), pdata(nullptr) {}
inline bool Match(MessageHandler* handler, uint32_t id) const {
return (handler == nullptr || handler == phandler) &&
(id == MQID_ANY || id == message_id);
}
Location posted_from;
// 接收到消息如何处理
MessageHandler* phandler;
uint32_t message_id;
// 消息内容
MessageData* pdata;
};</pre>

完了,完了,发生大事了,看到没有,是不是和你想的不一样?因为他有个phandler。我们一般都是接收消息,然后按照我们自己的逻辑去处理消息。人家这个不是这样,webrtc是我发送者发送消息的时候,同时还告诉你应该用phandler处理消息。接收者就是一个傀儡!无情的执行机器!!

看下人家代码里面怎么用的:

<pre class="md-fences mock-cm md-end-block md-fences-with-lineno" spellcheck="false" lang="cpp" cid="n412" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: pre; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 8px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; position: relative !important;">代码路径:.\rtc_base\thread.cc
class MessageHandlerWithTask final : public MessageHandler {
public:
MessageHandlerWithTask() {}

void OnMessage(Message* msg) override {
// 直接调用了MessageData的Run方法,也就是说我们具体执行实在MessageData的Run方法中
static_cast<rtc_thread_internal::MessageLikeTask*>(msg->pdata)->Run();
delete msg->pdata;
}

private:
~MessageHandlerWithTask() override {}

RTC_DISALLOW_COPY_AND_ASSIGN(MessageHandlerWithTask);
};

// 强制转成MessageLikeTask其实就是个interface 类:
class MessageLikeTask : public MessageData {
public:
virtual void Run() = 0;
};</pre>

从上面看到了吧,也就是说要想使用,那么你需要继承MessageData,并且重写Run方法。因为,MessageHandler其实最终会在OnMessage方法中调用这个Run方法,这也可以理解了,毕竟数据和方法应该放一块,要么你怎么处理呢?对吧

Dispatch:

直接看代码:

<pre class="md-fences mock-cm md-end-block md-fences-with-lineno" spellcheck="false" lang="cpp" cid="n416" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: pre; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 8px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; position: relative !important;">void Thread::Dispatch(Message* pmsg) {
TRACE_EVENT2("webrtc", "Thread::Dispatch", "src_file",
pmsg->posted_from.file_name(), "src_func",
pmsg->posted_from.function_name());
RTC_DCHECK_RUN_ON(this);
int64_t start_time = TimeMillis();
// 调用MessageHandler的OnMessage去处理消息
pmsg->phandler->OnMessage(pmsg);
int64_t end_time = TimeMillis();
int64_t diff = TimeDiff(end_time, start_time);
if (diff >= dispatch_warning_ms_) {
RTC_LOG(LS_INFO) << "Message to " << name() << " took " << diff
<< "ms to dispatch. Posted from: "
<< pmsg->posted_from.ToString();
// To avoid log spew, move the warning limit to only give warning
// for delays that are larger than the one observed.
dispatch_warning_ms_ = diff + 1;
}
}</pre>

3、线程间通信:

线程间通信有两种方式Invoke和Post:

  • Invoke:同步方式,也就是说我把消息放到你的消息队列要等你处理完了我的消息我再返回;

  • Post:异步方式,我丢到你消息队列就返回了;

Post方式:

使用方式随处可以见,比如:

<pre class="md-fences mock-cm md-end-block md-fences-with-lineno" spellcheck="false" lang="cpp" cid="n427" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: pre; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 8px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; position: relative !important;">network_thread_->Post(RTC_FROM_HERE, this, message_id, data);</pre>

看看具体代码:

<pre class="md-fences mock-cm md-end-block md-fences-with-lineno" spellcheck="false" lang="cpp" cid="n429" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: pre; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 8px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; position: relative !important;">/**

  • 异步投递消息
    /
    void Thread::Post(const Location& posted_from,
    MessageHandler
    phandler,
    uint32_t id,
    MessageData* pdata,
    bool time_sensitive) {
    RTC_DCHECK(!time_sensitive);
    if (IsQuitting()) {
    delete pdata;
    return;
    }

// Keep thread safe
// Add the message to the end of the queue
// Signal for the multiplexer to return

{
CritScope cs(&crit_);
Message msg;
msg.posted_from = posted_from;
msg.phandler = phandler;
msg.message_id = id;
msg.pdata = pdata;
// 你放到我的消息队列你就撤
messages_.push_back(msg);
}
// Wakeup一下,让之前的Wait逻辑继续执行
WakeUpSocketServer();
}</pre>

这儿的WakeUp函数如下:

<pre class="md-fences mock-cm md-end-block md-fences-with-lineno" spellcheck="false" lang="cpp" cid="n431" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: pre; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 8px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; position: relative !important;">void Thread::WakeUpSocketServer() {
ss_->WakeUp();
}</pre>

这个ss_就是SocketServer子类对象,前面也说过,分为NullSocketServer和PhysicalSocketServer,越讲越多,不展开了,后面可以单独写一篇;

Invoke方式:

使用方式比如:

<pre class="md-fences mock-cm md-end-block md-fences-with-lineno" spellcheck="false" lang="cpp" cid="n435" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: pre; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 8px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; position: relative !important;">network_thread_->Invoke<void>(
RTC_FROM_HERE, [this, rtp_transport] { SetRtpTransport(rtp_transport); });</pre>

看看具体代码:

<pre class="md-fences mock-cm md-end-block md-fences-with-lineno" spellcheck="false" lang="cpp" cid="n437" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: pre; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 8px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; position: relative !important;">// 定义在这儿,路径:.\rtc_base\thread.h
template <
class ReturnT,
typename = typename std::enable_if<!std::is_void<ReturnT>::value>::type>
ReturnT Invoke(const Location& posted_from, FunctionView<ReturnT()> functor) {
ReturnT result;
InvokeInternal(posted_from, [functor, &result] { result = functor(); });
return result;
}

template <
class ReturnT,
typename = typename std::enable_if<std::is_void<ReturnT>::value>::type>
void Invoke(const Location& posted_from, FunctionView<void()> functor) {
InvokeInternal(posted_from, functor);
}

void Thread::InvokeInternal(const Location& posted_from,
rtc::FunctionView<void()> functor) {
TRACE_EVENT2("webrtc", "Thread::Invoke", "src_file", posted_from.file_name(),
"src_func", posted_from.function_name());

class FunctorMessageHandler : public MessageHandler {
public:
explicit FunctorMessageHandler(rtc::FunctionView<void()> functor)
: functor_(functor) {}
void OnMessage(Message* msg) override { functor_(); }

private:
rtc::FunctionView<void()> functor_;
} handler(functor);
// 调用了Send函数
Send(posted_from, &handler);
}</pre>

Send里面注意一下,最后还调用了一次WakeUp,并且注释已经说得很明白了,我用我拙劣的CET4翻译了下:

<pre class="md-fences mock-cm md-end-block md-fences-with-lineno" spellcheck="false" lang="cpp" cid="n439" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: pre; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 8px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; position: relative !important;">
void Thread::Send(const Location& posted_from,
MessageHandler* phandler,
uint32_t id,
MessageData* pdata) {
RTC_DCHECK(!IsQuitting());
if (IsQuitting())
return;

// Sent messages are sent to the MessageHandler directly, in the context
// of "thread", like Win32 SendMessage. If in the right context,
// call the handler directly.
Message msg;
msg.posted_from = posted_from;
msg.phandler = phandler;
msg.message_id = id;
msg.pdata = pdata;
if (IsCurrent()) {
// 如果就是本线程,不用派遣消息,直接执行MessageHandler的OnMessage
msg.phandler->OnMessage(&msg);
return;
}

AssertBlockingIsAllowedOnCurrentThread();

Thread* current_thread = Thread::Current();

if RTC_DCHECK_IS_ON

if (current_thread) {
RTC_DCHECK(current_thread->IsInvokeToThreadAllowed(this));
ThreadManager::Instance()->RegisterSendAndCheckForCycles(current_thread,
this);
}

endif

// Perhaps down the line we can get rid of this workaround and always require
// current_thread to be valid when Send() is called.
std::unique_ptr<rtc::Event> done_event;
if (!current_thread)
done_event.reset(new rtc::Event());
// 不在同一线程,那么就调用PostTask派遣到对应线程
bool ready = false;
PostTask(webrtc::ToQueuedTask(
&msg mutable { msg.phandler->OnMessage(&msg); },
[this, &ready, current_thread, done = done_event.get()] {
if (current_thread) {
CritScope cs(&crit_);
ready = true;
// 执行一次WakeUp
current_thread->socketserver()->WakeUp();
} else {
done->Set();
}
}));

if (current_thread) {
bool waited = false;
crit_.Enter();
while (!ready) {
crit_.Leave();
current_thread->socketserver()->Wait(kForever, false);
waited = true;
crit_.Enter();
}
crit_.Leave();

// Our Wait loop above may have consumed some WakeUp events for this
// Thread, that weren't relevant to this Send.  Losing these WakeUps can
// cause problems for some SocketServers.
//
// Concrete example:
// Win32SocketServer on thread A calls Send on thread B.  While processing
// the message, thread B Posts a message to A.  We consume the wakeup for
// that Post while waiting for the Send to complete, which means that when
// we exit this loop, we need to issue another WakeUp, or else the Posted
// message won't be processed in a timely manner.
// 这个注释已经说明白了
// 上述 Wait 循环可能已消耗此线程的一些 WakeUp 事件,这些事件与此 Send 无关。丢失这些 WakeUp 可能会给某些 SocketServer 造成问题。
//
// 具体示例:
// 线程 A 上的 Win32SocketServer 调用线程 B 上的 Send。
// 在处理消息时,线程 B 向 A 发布消息。我们在等待 Send 完成时消耗该 Post 的唤醒,
// 这意味着当我们退出此循环时,我们需要发出另一个 WakeUp,否则将无法及时处理已发布的消息。
if (waited) {
  current_thread->socketserver()->WakeUp();
}

} else {
done_event->Wait(rtc::Event::kForever);
}
}</pre>

五、总结:

WebRTC线程还是相当复杂的,里面其实还有NullSocketServer和PhysicalSocketServer这个知识点比较重要,我没有分析。还有,之前创建PeerConnection的时候我们发现,上层用户调用我们的API时候,我们并不是直接去调用内部方法,而是使用了接口宏,好处就是实现和接口隔离,缺点就是接口宏展开非常复杂(相当于用宏创建了一个类),这儿也没有分析,但是,大家基本已经形成体系,不影响阅读代码了。宏这种东西,反正我写代码尽量不会用,严重影响debug。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容