理解WebKit和Chromium: 消息循环(Message Loop)
转载请注明出处:http://blog.csdn.net/milado_nju/
# 消息循环
## 概述
前面介绍了线程间如何传递chromium自定义任务(task),那么在线程内,消息循环(messageloop)是如何处理这所有的消息和任务呢?本章节重点介绍消息循环的工作原理。
在Chromium里,需要处理三种类型的消息:chromium自定义的任务,Socket或者文件等IO操作以及用户界面(UI)的消息。这里面,chromium自定义任务是平台无关的,而后面两种类型的消息是平台相关的。回忆一下前面多线程模型章节中列举的众多线程,例如主线程(UI线程)需要处理UI相关的消息和自定义任务;IO线程则需要处理Socket和自定义任务;History线程则只需要处理自定义任务;其它线程需要处理消息类型不会超出以上三个线程。根据三个组合,chromium定义和实现三种相对应的类来支持它们,下面让我们来看看具体的实现吧。
##Chromium中主要的类
这是本章中最重要的三个基类,依次介绍它们:
类RunLoop:一个辅助类,主要封装消息循环MessageLoop类,其本身没有特别的功能,主要提供一组公共接口被调用,其实质是调用MessageLoop类的接口和实现。
类MessageLoop:主消息循环,原理上讲,它应该可以处理三种类型的消息,包括支持不同平台的消息。事实上,如果让它处理所有这些消息,这会让其代码结构复杂不清难以理解。因此,根据上面对各个线程的分析,消息循环也只需要三种类型,一种仅能处理自定义任务,一种能处理自定义任务和IO操作,一种是能处理自定义任务和UI消息。很自然地,Chromium定义一个基类MessageLoop用于处理自定义任务,两个子类对应于第二和第三种类型。如下图所示。对于第二和第三种MessageLoop类型,它们除了要处理任务外,还要处理平台相关的消息,为了结构清晰,chromium定义一个新的基类及其子类来负责处理它们,这就是MessagePump。MessagePump的每个子类针对不同平台和不同的消息类型。事实上,不仅如此,消息处理的主循环也在MessagePump中,这有些令人意外,后面会详细介绍。因此,MessageLoop通过实现MessagePumpDelegate的接口来负责处理Chromium自定义任务。如下图所示。
类MessagePump: 一个抽象出来的基类,可以用来处理第二和第三种消息类型。对于每个平台,它们有不同的MessagePump的子类来对应,这些子类被包含在MessageLoopForUI和MessageLoopForIO类中。
结合上面的图,针对三种类型的消息循环,三种典型的线程在不同平台所使用的类如下:
|
主线程 |
IO线程 |
History线程 |
Linux |
MessageLoopForUI, MessagePumpGtk |
MessageLoopForIO, MessagePumpLibEvent |
MessageLoop, MessagePumpDefault |
Windows |
MessageLoopForUI, MessagePumpForUI |
MessageLoopForIO, MessagePumpForIO |
MessageLoop, MessagePumpDefault |
Mac |
MessageLoopForUI, MessagePumpMac |
MessageLoopForIO, MessagePumpLibEvent |
MessageLoop, MessagePumpDefault |
Android |
MessagePumpForUI Android.os.Looper SystemMessageHandler MessagePumpAndroid |
MessageLoopForIO, MessagePumpLibEvent |
MessageLoop, MessagePumpDefault |
对于所有平台来说,History线程所使用的类是一样的;UI线程和IO线程分别对应不同的MessagePump。相信大家注意到了,最后一个平台Android跟其它的稍有不同,那就是它的UI线程,原因在于主循环在Java层,用户界面的事件派发机制都在Java代码来处理,因而需要在Android的消息循环机制中加入对自定义任务的处理,后面会作介绍。
## 无限循环
这里面还有一个重要的部分需要介绍,那就是消息循环的主逻辑。该循环本质就是一个无限循环,不停的处理消息循环接收到的任务和消息,直到需要推出为止。如前面所述,主消息循环的逻辑在MessagePump中,而MessageLoop只是负责处理自定义的任务,MessagePump通过调用MessagePumpDelegate来达到该目的。下面是IO线程的消息处理主逻辑的时序图。
前面章节我们介绍过MessageLoop有任务队列来保存需要处理的任务,这些任务可能有不同的优先级,例如需要即时处理,或者延迟处理,或者Idle时处理。上图中,当调用DoWork时候,首先将出入任务队列(incoming task)拷贝到工作任务队列中,然后依次执行该队列中的任务。之后,同理处理调用DoDelayWork和DoIdleWork。当这些处理完后,它会阻塞和等待在IO操作。
对于Android的UI线程来说,情况稍有不同,因为主循环逻辑是由Java层的Android.os.Looper来控制的,所以MessagePumpAndroid其实是被Looper调用的。当它被调用时,其会把执行任务的工作交给MessagePumpDelegate,这里也就是MessageLoopForUI来完成,如下图所示的UI线程的消息循环主逻辑。Java层的SystemMessageHandler和C++层的MessagePumpAndroid其实都是辅助Looper调用执行chromium的自定义类的。
最后还有一个问题,就是如何等待自定义的任务。假设现在MessageLoop没有任务和消息需要处理,它应该等待Socket或者IO或者任务,OS系统支持Socket和IO唤醒它,问题是如何等待自定义任务呢?总不能忙式的检查吧,那样太耗费资源了。Chromium设计了一个巧妙的方法来解决该问题,以MessagePumpLibEvent为例:在Linux平台上,该类创建一个管道,它等待读取这个管道的内容,当有自定义的新任务到来时,写入一个字节到这个管道,从而MessageLoop被唤醒,非常地简单和直接。
## 源文件目录
base/message_ loop.h|cc
base/message_pump.h|cc
消息循环相关的众多类基本上都位于该目录中,文件名以”message_”开头
base/android/java/src/org/chromium/base/SystemMessageHandler.java
Android平台下支持消息循环的辅助类
## 参考文献
1. http://bigasp.com/archives/478
By yongsheng@chromium.org