2008年6月27日星期五

浅谈Gecko关键部分之三线程管理及主要线程

作为一个浏览器内核,Gecko所要完成的任务是非常繁杂的,其主要任务就是根据用户提供的资源地址,通过http协议从Web服务器中取得页面文档,然后解析其内容,最后根据一定的约定在浏览器指定区域中显示出页面,其中往往涉及网络编程及图形界面编程,而大家通常都知道的是网络编程中的连接、读取数据等往往需要考虑到服务器端的情况,一般采用异步方式来确保有效处理服务端返回的数据包括连接不成功、错误处理等;而图形界面的处理往往需要采用一个主消息循环及回调函数的方式来处理用户的动作,为了给用户提供平滑的操作及兼顾后台服务器的不确定性,一个可行的浏览器内核必须充分利用多线程来协调处理复杂的应用场景,只有这样才能高效的完成其所要完成的任务,如果能够初步了解Gecko内核的线程模型及相关线程管理的知识,对了解Gecko是非常有帮助的,下面初步了解Gecko是如何进行线程管理及其主要线程实现。

一、Gecko线程模型
为了统一接口编程,Gecko将其线程模型按照组件的方式来处理,定义的接口主要有nsIRunnable、nsIEventTarget、nsIThread、nsIThreadManager、nsIThreadPool,其中nsIThreadManager由nsThreadManager来实现,它主要用于来管理所有的nsThread,包括创建nsThread实例,并通过维护一些系统原生线程的属性可以判断同一段代码是在什么线程的上下文中调用,从而可作出不同的处理,如GetIsMainThread可以判定当前执行线程是否为主线程,这一点在Gecko中应该得到了充分的应用,同时nsThreadManager的实例属于sington模式,在第一次启用XPCOM组件时由NS_InitXPCOM3实例化出来,以供以后管理nsThread使用;

nsThread代表一个一般意义上的原生线程,在其构造函数中会初始化它并启动原生线程,它同时增加了EventQueue及ThreadObserver的概念,其函数入口为nsThread::ThreadFunc,在线程的整个周期中也即nsThread::ThreadFunc中不断的处理外部线程或本身向其EventQueue中Dispatch来的Event,同时每处理一个Event之前都会检查其是否存在Observer,如存在则先让Observer调用其OnProcessNextEvent来处理该Event,然后调用Event本身提供的run方法,这样增加了nsThread处理Event的灵活性;同时nsThread统一由nsThreadManager来创建,通过调用其Shutdown方法来结束该线程;并提供了一组外部接口来进行线程管理操作,如
NS_NewThread、NS_GetMainThread、NS_DispatchToMainThread、NS_ProcessNextEvent等以供不同线程调用,同时保证线程安全;

nsThreadPool代表一组线程,当外部向其Dispatch一个Event时它先会向其中的EventQueue添加该Event,然后它会根据设置的参数来决定是否分配一个nsThread来处理该Event,一旦创建了一些nsThread则将其维护在mThreads队列中,同时向该nsThread Dispatch这个nsThreadPool实例以让该nsThread去执行该nsThreadPool的run方法,这样可保证线程池中的每个nsThread的执行体都是nsThreadPool中的run方法,在其run方法中会根据是否空闲等条件自动ShutDown一些暂时不用的nsThread,以达到线程池的目的,即当任务事件Event繁多的时候多开启一些线程来处理,一旦任务完成则释放大部分空闲线程,保持一小部分线程以等待新任务的分配及提交;nsThreadPool在异步读取网络流数据的时候会经常用到,以后有机会可具体分析其应用场景;

二、Gecko主要线程
MainThread是Gecko的主线程,也即进程启动时的执行序列,它主要处理图形界面的消息循环以及其他线程向它Dispatch过来的Event,这样可以有机的结合图形界面的处理及网络数据的读取、解析、渲染等。MainThread线程充分利用了Gecko线程模型特点,既能处理原生的窗口消息,又可及时处理其它线程通知给它的Event,其执行主体往往是Gecko的核心,据初步统计其执行时间往往占整个程序执行时间的90%以上,其效率的高低直接决定了是否会让用户产生阻塞停顿的感觉。其主要实现逻辑是在启动XPCOM当中会在nsThreadManager初始化时自动产生一个nsThread实例mMainThread,它代表进程启动时的执行序列,待该执行序列完成其他基本准备后,在nsBaseAppShell::Init中将其Observer设置为nsBaseAppShell实例本身,具体参考如下:

nsresult nsBaseAppShell::Init()
{ // Configure ourselves as an observer for the current thread:
nsCOMPtr threadInt = do_QueryInterface(NS_GetCurrentThread()); NS_ENSURE_STATE(threadInt);
threadInt->SetObserver(this);//mObserver=>this
nsCOMPtr obsSvc = do_GetService("@mozilla.org/observer-service;1");
if (obsSvc) obsSvc->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, PR_FALSE);
return NS_OK;
}

NS_IMETHODIMP nsBaseAppShell::Run(void)
{
nsIThread *thread = NS_GetCurrentThread();
NS_ENSURE_STATE(!mRunWasCalled); // should not call Run twice mRunWasCalled = PR_TRUE;
while (!mExiting) NS_ProcessNextEvent(thread);
NS_ProcessPendingEvents(thread);
return NS_OK;
}

PRBool NS_ProcessNextEvent(nsIThread *thread, PRBool mayWait)
{
nsCOMPtr current;
if (!thread) {
NS_GetCurrentThread(getter_AddRefs(current));
NS_ENSURE_TRUE(current, PR_FALSE);
thread = current.get();
}
PRBool val;
return NS_SUCCEEDED(thread->ProcessNextEvent(mayWait, &val)) && val;
}

NS_IMETHODIMP nsThread::ProcessNextEvent(PRBool mayWait, PRBool *result)
{
..........
nsCOMPtr obs = mObserver;
if (obs) obs->OnProcessNextEvent(this, mayWait && !ShuttingDown(), mRunningEvent);

mEvents->GetEvent(mayWait && !ShuttingDown(), getter_AddRefs(event));
*result = (event.get() != nsnull);
nsresult rv = NS_OK;
if (event) {
++mRunningEvent;
event->Run();
--mRunningEvent;
}
else if (mayWait)
{ .........}

if (obs) obs->AfterProcessNextEvent(this, mRunningEvent);
..........
return rv;
}

其中关键点在于MainThread的mObserver是nsAppShell实例,那么它的OnProcessNextEvent都作了些啥呢?主要是根据情形调用其ProcessNextNativeEvent方法,其实现如下:

PRBool nsAppShell::ProcessNextNativeEvent(PRBool mayWait)
{
PRBool gotMessage = PR_FALSE;
do
{
MSG msg; // Give priority to system messages (in particular keyboard, mouse, timer, // and paint messages).
if (PeekKeyAndIMEMessage(&msg, NULL) ::PeekMessageW(&msg, NULL, WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE) ::PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE))
{
gotMessage = PR_TRUE;
if (msg.message == WM_QUIT)
{
Exit();
}
else {
::TranslateMessage(&msg);
::DispatchMessageW(&msg);
}
}
else if (mayWait) {
// Block and wait for any posted application message
::WaitMessage();
}
} while (!gotMessage && mayWait);
return gotMessage;
}
这个函数是否觉得非常的面熟???

nsSocketTransportService作为异步处理socket读写的线程,在程序启动就会实例化,有时间可以仔细看看\mozilla\network\base\src\nsSocketTransportService2.cpp,其主要代码应该与一般多线程网络程序差不多。

至于其他关于TimerThread、ThreadPool及javascript垃圾回收线程等以后有时间再来分析。

三、参考网址
The Thread Manager
XPCOM:nsIThreadManager