随着CPU频率接近物理极限,多芯片、多核几乎成为了加速软件运行速度的唯一选择。与之相应地,多线程、异步编程以及并发编程成为了软件开发人员的必修课。因此,各种各样的开发框架不断涌现。在C++领域,boost的thread库等优秀的多线程框架也是其中的代表。特别是针对socket网络编程的boost.asio库可以轻松帮助开发人员实现多线程大量并发的网络服软件。
与这些框架相比,WebRTC的多线程模型还是比较特别的。它的核心是类似于WSAWaitForMultipleEvents的多路信号分离器。这是我最喜欢的多线程模型。它使每条线程既可以处理消息(post、send),同时也可以处理多个IO。并且在必要的情况下让一条线程独占(管理)一些资源,避免过多的使用锁造成线程死锁。个人认为这种模型可以帮助开发人员更容易地实现快速响应的界面程序。
WebRTC的线程模块放在/trunk/talk/base目录下,namespace使用talk_base::。主要涉及criticalsection、event、messagequeue、thread、messagehandler、physicalsocketserver等文件。顺便提一下,现在网上仅有的几篇关于WebRTC或是libjingle的文章多数从Sigslot开始讲解,但是我不打算现在对Sigslot进行展开,因为它还是很好理解的。不明白的朋友可以上网查询一下,或者姑且就把它当做函数回调(更精确地说它实现了C#的delegate)就可以了。在talk_base的多路信号分离器中只有少数几个地方使用了Sigslot,即使忽略它也不影响对多路信号分离器的理解。
1 event
event.h/event.cc文件中只有talk_base::Event类。
1.1 talk_base::Event
该类主要实现了跨平台的Win32 Event功能(正如前言中所说,本文假定读者已经很熟悉Win32平台的各种组件,如有不明白的地方可以参考MSDN)。talk_base::Event类的各个成员函数与Win32 Event所提供的API几乎一致,所以不再多做解释。
在Linux系统中,WebRTC使用了mutex和条件变量来实现Event的功能。首先,我将对Win 32 API和pthread API做一下类比:
CreateEvent
pthread_mutex_init、pthread_cond_init这两个函数用来创建pthread的mute和条件变量。
CloseHandle
pthread_mutex_destroy、pthread_cond_destroy这两个函数用来销毁pthread的mute和条件变量。
SetEvent
pthread_mutex_lock、pthread_mutex_unlock这两个函数加锁和解锁mutex,pthread_cond_broadcast函数用来解除所有等待在该条件变量上的线程的阻塞状态。
ResetEvent
pthrea_mutex_lock、pthread_mutex_unlock(已解释)
WaitForSingleObject
pthrea_mutex_lock、pthread_mutex_unlock(已解释)。pthrea_cond_wait函数用来使线程阻塞在条件变量上。
下面我将大致解释一下talk_base::Event类的实现原理:
talk_base::Event的主要功能由条件变量实现,mutex只是辅助条件变量起到锁的作用。条件变量的pthread_cond_wait和pthread_cond_broadcast函数与Win32 Event的WaitForSingleObject和SetEvent基本类似。talk_base::Event是否为signal状态由布尔类型的成员变量event_status_控制。是否为manual reset的Event由布尔类型的成员变量is_manual_reset_控制。与Win32 Event不同的状况主要体现在Event的manual reset控制上。
Linux系统下所有调用talk_base::Event::Wait函数的线程会阻塞在pthread_cond_wait函数上。当talk_base::Event::Set函数被调用时,pthread_cond_broadcast函数会解除所有等待在pthread_cond_wait函数上的线程的阻塞状态。这对于manual reset的Win32 Event来说没什么问题,问题出在auto reset的Win32 Event上。Auto reset的Win32 Event每次只能解除一条等待在Event上的线程的阻塞状态,其他线程依然为阻塞状态。这就需要mutex来配合实现了。
在这里要重点解释一下pthread_cond_wait函数的第二个参数pthread_mutex_t *mutex。当线程进入pthread_cond_wait函数时会解锁mutex,而在离开pthread_cond_wait时会重新加锁mutex。可以理解为:
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex)
{
pthread_mutex_unlock(mutex);
…
…
…
pthread_mutex_lock(mutex);
return 0;
}
这是Win32 没有的行为,需要特别注意。
有了以上的机制后,模拟auto rest的Win32 Event就没问题了。当第一条线程获得mutex锁并离开pthread_cond_wait函数时,其他线程会依然被阻塞在pthread_mutex_lock(mutex)函数上,无法离开pthread_cond_wait函数。那条成功离开线程会马上检测当前的talk_base::Event是否为manual reset的,如果不是就马上将event_status_成员变量设置为false,并解锁mutex。这时其他线程才能有机会离开pthread_cond_wait函数。不过当他们离开pthread_cond_wait后立即检测event_status_成员变量,如果为false就重新调用pthread_cond_wait函数。这就完美实现了Win32 Event的auto reset的语义。
条件变量和mutex的配合是talk_base::Event类的难点。如果读者还是不能完全理解,请仔细阅读以上3段的内容(也可以上网查找pthread_cond_wait函数),并结合event.cc的源代码反复揣摩,应该可以很快理解的(毕竟代码不多,而且也不是WebRTC中真正困难的部分),我就不再多做解释了(在后面的部分我会提供独立编译\trunk\talk\base\目录下的源代码的makefile脚本,读者可以编译后添加调试代码分析talk_base::Event的原理)。
在分析了talk_base::Event的源代码之后有个疑问,那就是为什么不用pthread_cond_signal函数实现Win32 Event的auto reset语义?由于时间的原因暂且不做深入地研究。