WebRTC源代码探索之旅——多线程篇-6

来源:互联网 发布:知名网络小说家 编辑:程序博客网 时间:2024/05/18 16:36

6 thread

 

thread.h/thread.cc文件实现了WebRTC对系统线程的封装。它主要包括3个类talk_base::ThreadManager类、talk_base::Thread类和talk_base::Runnalbe类;以及几个工具类talk_base::AutoThread类、talk_base::ComThread类和talk_base::SocketServerScope类。

 

6.1 talk_base::ThreadManager

 

talk_base::ThreadManager类虽然比较复杂,但是它实现的功能还是比较简单的——帮助talk_base::Thread类实现talk_base::Thread::Current静态函数功能。talk_base::Thread::Current静态函数的主要功能就是获取代表当前线程的talk_base::Thread实例。这就要求在创建系统线程的同时能够立即将它自动封装成talk_base::Thread并加以合理地保存。这就是talk_base::ThreadManager的主要功能。

 

talk_base::ThreadManager的主要组件包括:

key_:系统TLS(Thread LocalStorage)的key。在Windows系统上它是一个DWORD变量,在Linux系统上它被定义为pthread_key_t。

TLS能够在线程空间内保存一些数据,所有的线程通过它只能访问到自己线程空间的变量,各个线程之间互不影响。它也是一个重要的多线程编程的工具。到这里talk_base::ThreadManager的基本原理其实已经可以猜得出来了,其实talk_base::ThreadManager就是将封装线程的talk_base::Thread类的实例指针存放在TLS内。线程内运行的代码在调用talk_base::Thread::Current静态函数的时候,talk_base::ThreadManager就从当前线程的TLS内取出该指针并返回。

 

talk_base::ThreadManager的主要函数包括:

talk_base::ThreadManager::WrapCurrentThread:将当前线程封装成一个talk_base::Thread对象,并存储在TLS中。

参数说明:无

 

前面已经提到了talk_base::ThreadManager的基本原理是使用TLS,这里将更具体的讲解它工作的过程。talk_base::ThreadManager本身是一个伪装成singleton的类,但是它没有释放的过程,这个手法在前面的talk_base::PosixSignalHandler中已经讲解过了。talk_base::ThreadManager在构造的时候会将当前线程自动包装成talk_base::Thread。为了达到绝对保证主线程先于其他线程被包装成talk_base::Thread,每个系统线程启动的时候都会在talk_base::Thread::Start函数中调用talk_base::ThreadManager::Instance函数以确认talk_base::ThreadManager实例已经被创建。而在这工程中talk_base::Thread::Start函数是在父线程中被调用的。以上这些保证了在第一次创建子线程之前talk_base::ThreadManager已经在单线程环境中被创建(),并且主线程被封装成talk_base::Thread对象。每个线程的启动函数talk_base::Thread::PreRun函数(在新线程的空间中运行)会调用talk_base::ThreadManager::SetCurrentThread函数将新创建的talk_base::Thread对象放入系统线程的TLS中使它纳入到talk_base::ThreadManager的管理之下。当调用talk_base::ThreadManage::CurrentThread函数时,talk_base::ThreadManager仅仅从TLS中返回代表当前线程的talk_base::Thread对象。

 

对照Windows版和Linux版的代码,我们可以发现2者基本一致。那我们来看一下两者API的对比:

OpenThread、GetCurrentThreadId

pthread_self函数用于获取当前线程的pthread_t。在Windows平台下一般可以使用线程ID或线程句柄代表当前线程,而在Linux平台下使用pthread_t代表当前线程。

 

TlsAlloc

pthread_key_create函数用于创建一个TLS的key

 

TlsGetValue

pthread_getspecific函数用于从TLS中取出数据

 

TlsSetValue

pthread_setspecific函数用于TLS中存放数据

 

TlsFree

pthread_key_delete函数用于删除一个TLS的key

 

6.2 talk_base::Runnalbe

 

该类定义了talk_base::Runnalbe纯虚基类(接口),如果希望定制talk_base::Threaad::Run函数的行为可以继承talk_base::Runnable类,并将实例传递给talk_base::Thread::Start函数

6.3 talk_base::Thread

 

talk_base::Thread的主要功能是封装线程,为线程提供消息队列和多路信号分离器的功能。需要注意的是,talk_base::Thread的绝大多数功能是talk_base::MessageQueue和talk_base::PhysicalSocketServer的代码提供的,仅有talk_base::Thread::Send函数的功能主要由talk_base::Thread的代码提供。

 

talk_base::Thread的主要组件包括:

sendlist_:通过talk_base::Thread::Send函数请求处理的消息列表

running_:标示系统线程是否在正在运行的talk_base::Event,该成员变量表示线程正在运行

thread_:保存系统的线程句柄/pthread_t对象

owned_:标示talk_base::Thread对象是否是线程的所有者,该变量的语义和大家的直觉可能很不一样(至少和我的直觉不一样,造成了不少麻烦)。由talk_base::Thread::Start函数启动的系统线程,该成员变量为true;通过talk_base::Thread::WrapCurrent函数包装的线程为false。并不是talk_base::Thread实例对应的哪个线程就占有(own)哪个线程,甚至有些情况下talk_base::Thread实例是唯一的封装系统线程的实例,并通过talk_base::Thread::WrapCurrent设置到了TLS中去,但是这个实例依然不占有系统线程。唯有通过talk_base::Thread::Start函数启动的系统线程,该实例才占有那个系统线程。这个概念很重要,它会影响很多函数的行为。

 

talk_base::Thread的主要成员函数包括:

talk_base::Thread::Current(静态):获取封装当前线程的talk_base::Thread对象,该功能由talk_base::ThreadManager提供

参数说明:无

 

talk_base::Thread::IsCurrent:测试该talk_base::Thread对象是否是封装当前线程的对象。只有在TLS中保存的talk_base::Thread对象才是封装当前线程的对象。在当前线程构造的talk_base::Thread对象不一定是封装当前线程的对象(这点很重要,它将影响多个函数的行为)。

参数说明:无

 

talk_base::Thread::Start:启动线程

参数说明:

runnable:如果为NULL,则调用talk_base::Thread::ProcessMessages函数处理消息循环运行线程;如果不是NULL,则调用runnable->Run运行线程。

 

talk_base::Thread::Stop:停止消息循环,并调用talk_base::Thread::Join等待线程结束

参数说明:无

 

talk_base::Thread::Send:向一个线程发送消息,并等待消息完成,被请求线程可以是当前线程

参数说明:

phandler:消息处理器

id:消息ID

pdata:消息数据

 

talk_base::Thread::Invoke:请求在一个线程上执行函数,被请求的线程可以是当前线程

模板参数说明:

ReturnT:仿函数的返回值类型

FunctorT:仿函数的类型

参数说明:

functor:被请求执行的仿函数

 

talk_base::Thread::Clear:继承自talk_base::MessageQueue::Clear函数,为了保持父类该函数的语义需要同时删除sendlist_列表中的消息

参数说明:

phandler:消息处理器

id:消息ID

pdata:消息数据

 

talk_base::Thread::ReceiveSends:处理Send函数发来的消息

参数说明:无

 

talk_base::Thread::WrapCurrent/UnwrapCurrent:封装/解除封装当前线程,该功能由talk_base::ThreadManager提供

参数说明:无

 

talk_base::Thread::running(私有):线程是否运行。只有使用该实例启动一个系统线程(talk_base::Thread::Start)或者包装一个线程(talk_base::Thread::WrapCurrent)后,该函数才会返回true;

参数说明:无

 

接下来我将简单讲解一下talk_base::Thread的原理。talk_base::Thread的绝大多数功能由talk_base::MessageQueue、talk_base::PhysicalSocketServer和talk_base::ThreadManager类提供。talk_base::Thread的大多数代码是用来实现talk_base::Thread::Send函数的功能。talk_base::Thread::Send函数接受到消息后判断是否当前线程是否就是被请求的线程。如果当前线程即为被请求的线程,立即处理消息。如果当前线程不是被请求线程,将消息加入到被请求线程的talk_base::Thread::sendlist_列表。然后通过talk_base::MessageQueue::ss_->WakeUp函数解除被请求线程的IO阻塞状态,并且通过current_thread->socketserver()->Wait函数让自己等待在IO阻塞中。被请求线程的消息循环talk_base::MessageQueue::Get函数会调用talk_base::Thread::ReceiveSends(虚)函数处理sendlist_中的消息。被请求线程处理完消息后会调用请求线程的thread->socketserver()->WakeUp以解除它的IO阻塞状态。请求线程通过局部变量ready获知被请求线程已经完成了消息处理。整个talk_base::Thread::Send函数在请求线程和被请求线程之间的交互操作流程大致如此。

 

最后,让我们对比下Windows API和Linux API在talk_base::Thread类中的使用状况:

Sleep

nanosleep函数用于将当前线程阻塞等待指定的时间

 

SetThreadPriority

在Linux版本的talk_base::Thread中没有实现该功能

 

CreateThread

pthread_attr_init函数用于初始化pthread的线程属性;pthread_create函数用于创建系统线程

 

修改线程的名字

在Windows平台上使用了一个比较晦涩的方法实现:调用RaiseException(0x406D1388, ... )函数;但是在Linux版本的talk_base::Thread中没有实现该功能(其实这个功能没什么大的作用)。

 

WaitForSingleObject、CloseHandle

pthread_join用于等待系统线程结束。

6.4 talk_base::AutoThread

 

该类是一个工具类,它的主要功能是临时封装一个线程。用户的应用程序自行调用了系统API创建了一个线程,而这条线程需要临时使用一下talk_base::Thread的功能,比如调用另一个线程talk_base::Thread::Send并阻塞等待另一条线程完成函数调用。是的,在talk_base::Thread中talk_base::Thread::Send就用到了talk_base::AutoThread来确保发起Send调用的线程具备等待调用返回的能力(如果发起Send的线程已经被talk_base::Thread封装,那么talk_base::AutoThread将不起作用)。

 

接下来让我们看一下WebRTC是如何是如何实现talk_base::AutoThread的。首先,在它的构造函数中会检查TLS。如果在TLS已经保存了封装当前线程的talk_base::Thread,则什么事情都不做;如果没有,就将当前构造talk_base::AutoThread设置入TLS。需要注意的是“设置”操作,即调用talk_base::ThreadManager::SetCurrentThread函数,不是包装,该函数仅仅将talk_base::AutoThread的指针村放入TLS。没有激发talk_base::AutoThread::running_(它不是布尔变量,而是一个talk_base::Event类型的变量),所以调用talk_base::Thread::running返回值为false。析构函数中虽然调用了talk_base::Thread::Stop函数,但是因为没有激发talk_base::Thread::running_,结果是什么都没做。最后检查TLS,如果存放着指向自己(talk_base::AutoThread)的指针就将他设置为NULL。

 

6.5 talk_base::ComThread

 

该类是一个很简单的辅助工具类,就是帮助在Windows平台下需要使用COM组件的线程在启动的时候调用CoInitializeEx,并在线程结束的时候调用CoUninitialize。作为Windows平台的开发人员,对这两个函数应该是非常熟悉的。

 

6.6 talk_base::SocketServerScope

 

该类也是一个非常简单的辅助工具类,主要功能就是临时替换当前线程的talk_base::SocketServer的实现。它在构造函数中调用talk_base::MessageQueue::set_socketserver函数将临时使用的talk_base::SocketServer提交给当前线程的talk_base::MessageQueue,并在析构函数中换回原来的talk_base::SocketServer。

 

到此为止,WebRTC的线程模型相关的代码基本已经介绍完毕。在结合阅读源代码的前提下,通读这6个章节的内容应该可以很顺畅地理解它们的工作原理以及调用关系。不过,可能有些读者在全局的理解上依然觉得比较困难。没有关系,我们将在下一章节使用2段小代码讲解一下整个线程模型如何工作。
0 0
原创粉丝点击