WebRTC学习进阶之路系列总目录:https://blog.youkuaiyun.com/xiaomucgwlmx/article/details/103204274
前言
WebRTC源码中的很多注释是很赞的,看源码的时候多加阅读注释有助于更好的理解。
WebRTC实现了跨平台(Windows,MacOS,Linux,IOS,Android)的线程类rtc::Thread,WebRTC内部的network_thread,worker_thread,signaling_thread均是该类的实例。
基础功能
rtc:: Thread及其相关类,ThreadManager、MessageQueue,Runnable等等一起提供了如下的基础功能:
线程的管理:通过ThreadManager单例对象,可以管理所有的Thread实例;
线程的常规基本功能:rtc:: Thread提供创建线程对象,设置线程名称,启动线程去执行用户代码;
消息循环,消息投递:rtc:: Thread通过继承MessageQueue类,提供了内部消息循环,并提供了线程间异步,同步投递消息的功能;
跨线程执行方法:提供了跨线程执行方法,并返回执行结果的功能。该功能非常强大,因为WebRTC在某些功能模块的使用上,有要求其必需在指定的线程中才能调用的基本要求,比如音频模块:ADM 的创建必须要在 WebRTC 的 worker thread 中进行;
多路分离器:通过持有SocketServer对象,实现了多路分离器的功能,能处理网络IO;
signaling_thread:处理小工作量方法,要求此线程内的方法都必须快速返回。
worker_thread:处理大工作量的方法,此线程内的方法可能会处理很长时间。
network_thread:处理网络消息。
下边我们来看下两个线程的核心类:ThreadManager和Thread。
一、ThreadManager
ThreadManager的源码位于rtc_base目录下的thread.h与thread.cc中,相关头文件如下:
class RTC_EXPORT ThreadManager {
public:
static const int kForever = -1;
// Singleton, constructor and destructor are private.
static ThreadManager* Instance();
Thread* CurrentThread();
void SetCurrentThread(Thread* thread);
Thread* WrapCurrentThread();
void UnwrapCurrentThread();
bool IsMainThread();
private:
ThreadManager();
~ThreadManager();
#if defined(WEBRTC_POSIX)
pthread_key_t key_;
#endif
#if defined(WEBRTC_WIN)
const DWORD key_;
#endif
// The thread to potentially autowrap.
const PlatformThreadRef main_thread_ref_;
RTC_DISALLOW_COPY_AND_ASSIGN(ThreadManager);
};
我们来逐步看下这几个方法:
1,static ThreadManager* Instance()
ThreadManager* ThreadManager::Instance() {
static ThreadManager* const thread_manager = new ThreadManager();
return thread_manager;
}
从源码可以看到ThreadManager是通过单例模式,通过静态方法Instance()来获取唯一的实例。
2,构造与设置、获取当前线程(跨平台)
#if defined(WEBRTC_POSIX)
ThreadManager::ThreadManager() : main_thread_ref_(CurrentThreadRef()) {
#if defined(WEBRTC_MAC)
InitCocoaMultiThreading();
#endif
pthread_key_create(&key_, nullptr);
}
Thread* ThreadManager::CurrentThread() {
return static_cast<Thread*>(pthread_getspecific(key_));
}
void ThreadManager::SetCurrentThread(Thread* thread) {
#if RTC_DLOG_IS_ON
if (CurrentThread() && thread) {
RTC_DLOG(LS_ERROR) << "SetCurrentThread: Overwriting an existing value?";
}
#endif // RTC_DLOG_IS_ON
pthread_setspecific(key_, thread);
}
#endif
#if defined(WEBRTC_WIN)
ThreadManager::ThreadManager()
: key_(TlsAlloc()), main_thread_ref_(CurrentThreadRef()) {}
Thread* ThreadManager::CurrentThread() {
return static_cast<Thread*>(TlsGetValue(key_));
}
void ThreadManager::SetCurrentThread(Thread* thread) {
RTC_DCHECK(!CurrentThread() || !thread);
TlsSetValue(key_, thread);
}
#endif
构造与析构函数均声明为private。下边看下ThreadManager在不同平台上的构造方式、设置和获取当前线程的系统差异性:
下边是来自网友ice_ly000的一段精辟的阐述,摘录部分如下:我们可以看到在Windows和类Unix系统中实现进行了区分,WEBRTC_POSIX宏表征该系统是类Unix系统,而WEBRTC_WIN宏表征是Windows系统。虽然实现稍微有些许不同,在MAC下还需要调用InitCocoaMultiThreading()方法来初始化多线程库。但是两个构造函数均初始化了成员key_与main_thread_ref_(我们可以看到WebRTC中的私有成员均以下划线结尾)。其中key是线程管理的关键。
key_的初始化:在Windows平台上,key_被声明为DWORD类型,赋值为TlsAlloc()函数的返回值,TlsAlloc()函数是Windows的系统API,Tls表示的是线程局部存储Thread Local Storage的缩写,其为每个可能的线程分配了一个线程局部变量的槽位,该槽位用来存储WebRTC的Thread线程对象指针。如果不了解相关概念,可以看微软的官方文档。在类Unix系统上,key_被声明pthread_key_t类型,使用方法pthread_key_create(&key_, nullptr);赋值。实质是类Unix系统上的线程局部存储实现,隶属于线程库pthread,因此方法与变量均以pthread开头。总之,在ThreadManager的构造之初,WebRTC就为各个线程所对应的Thread对象制造了一个线程局部变量的槽位,成为多线程管理的关键。
main_thread_ref_的初始化:该成员为PlatformThreadRef类型的对象,赋值为CurrentThreadRef()方法的返回值,如源码所示:在Windows系统下,取值为WinAPI GetCurrentThreadId()返回的当前线程描述符,DWORD类型;在FUCHSIA系统下(该系统是Google新开发的操作系统,像Android还是基于Linux内核属于类Unix范畴,遵循POSIX规范,但FUCHSIA是基于新内核zircon开发的),返回zx_thread_self(),zx_handle_t类型;在类Unix系统下,通过pthread库的pthread_self()返回,pthread_t类型。总之,如前文所述,这部分代码肯定是在主线程中所运行,因此,main_thread_ref_存储了主线程TID在不同平台下的不同表示。
感谢ice_ly000!
3,WrapCurrentThread()与UnwrapCurrentThread()
// Returns a thread object with its thread_ ivar set
// to whatever the OS uses to represent the thread.
// If there already *is* a Thread object corresponding to this thread,
// this method will return that. Otherwise it creates a new Thread
// object whose wrapped() method will return true, and whose
// handle will, on Win32, be opened with only synchronization privileges -
// if you need more privilegs, rather than changing this method, please
// write additional code to adjust the privileges, or call a different
// factory method of your own devising, because this one gets used in
// unexpected contexts (like inside browser plugins) and it would be a
// shame to break it. It is also conceivable on Win32 that we won't even
// be able to get synchronization privileges, in which case