chrome中有很多闪光点地方,它的消息系统就一快纯金,要看chrome 源码,必须要过消息系统这关。
本来这本部打算写在上一章的,考虑内容涵盖范围太广的,打算另开一章来写 chrome的消息系统,回头在去上一章做个比较有概括力的总结。
本章的思路是按照一下逻辑来展开的:
1:消息系统的概述(消息系统静态模型和动态模型的一个简单的介绍)
2:一个消息系统的生死因果(细说MessageLoopUI类)
3:消息系统的分类(对chrome的几种消息做消息介绍)
1:消息系统的概述
(1)消息系统的静态结构
这是消息系统的第一个部分,先看看和消息相关的静态类图:
(图片画的太大了,这张看的不是很清楚,需要大图片,可以去我的新浪空间去看 大图片)
上面那张图片涉及到设计软件时几种采用的伎俩,下面会一一分析:
伎俩一:handle-body,pimpl, 桥接
MessageLoop基本就是MessagePump的一个代理类,
扩充了一些MessagePump没有的功能,
chrome本意可以能是想让Message来处理所有事务,
而这个messgaePump可以做到对调用者透明。
但是对于研究chrome代码的CODER来说,
了解MessagePump是不可推卸的责任。
后面都会详细分析的。
MessagePump和MessageLoop采用用了一种handle-body或称pimpl的伎俩,
按照牛逼点的叫法叫着桥接,
关于此伎俩可以参考四人帮的设计模式。
伎俩二:委托
Chrome中已经用烂了改伎俩,基本上所有代码中都能看见此手法。
一个类的内部操作委托给外面的一个对象,通过抽象接口。
如上面代码所示,MessagePump把 DoWork DoDelayWork, DoIdelWork通过MessagePump::Deletgate 的接口委托给M essageLoop去处理,这样比较好的解决了代码间的耦合问题。
类似伎俩在消息系统这块还有很多:
MessagePump::Watcher 《对应handle的事件处理》
MessagePump::Observer 《监视Windows消息的处理》
MessagePump::Dispatcher 《Windows本地消息的分派》
MessageLoop::DestructionObserver 《用来跟踪MessageLoop的稀构》
这些都是委托的接口,只不过名字不像MessagePump::Deletgate这么张扬而已。
把委托的接口类写到类的内部,
对于chrome这种写法我是我以前没有考虑过的,
是个非常清晰的代码风格,
coder一看就能知道这个委托接口是干吗的。
伎俩三:线程本地存储
一个线程怎么获取到当前线程的消息循环对象,通过线程本地存储。
所谓的线程本地存储就是把一个变量和一个线程相关联.
最简单的说是我定义了一个全局的 int n变量,我把n设置为线程本地存储变量,当开启了4个线程,这四个线程都修改了 n这个变量,结果是n对应于每个线程都有一份拷贝,即四个线程四份n的值。
Chrome中是把 MessageLoop对象设置为线程本地存储,
每个线程获取线程的消息循环很简单,直接通过调用MessageLoop一个静态的current()方法返回当前线程的消息循环。
比如在一个线程里面运行一个消息循环
- MessageLoop::current()->Run();
伎俩四:关于chrome 添加任务的方式
注意观察MessageLoop中的任务队列,会发现它有四个队列,如下:
incoming_queue_ 《所有任务都是首先添加到该队列中》
work_queue_ 《当前的工作队列》
delayed_work_queue_ 《推迟指定时间执行的任务队列》
deferred_non_nestable_work_queue_《不能重入被推迟执行任务队列》
现在要弄明白每个队列的在MesageLoop中的语意,只有先了解了语意才能知道为什么要这样设计。
incoming_queue_ 和 work_queue_ 队列的区别?
由于添加队列设计到多线程操作,必然需要对其加锁。
程序的设计原则是能不用锁打死也不用,现在chrome的MessageLoop需要段的加入消息,循环内部需要不断地处理消息,如果每次添加和取消息都要对队列加锁,那么是比会影响其执行效率。
如是,chrome采用了这样的一个措施,采用两个队列:
一个用来从外部线程收集任务的队列(incoming_queue),一个是供内部使用的队列(work_queue),并且保证从work_queue队列中取出任务不需要加锁。
遵循以下三个原则:
1:每次添加一个任务都需要加锁(任务被添加到incoming_queue)
2:每次从work_queue队列中取出任务不需要加锁。
3:在work_queue为空时需要把incoming_queue队列中的内容取出来,取出的操作需要加锁
这样循环的效率可以提高 40-50%。
- void MessageLoop::ReloadWorkQueue() {
- // We can improve performance of our loading tasks from incoming_queue_ to
- // work_queue_ by waiting until the last minute (work_queue_ is empty) to
- // load. That reduces the number of locks-per-task significantly when our
- // queues get large.
- if (!work_queue_.empty())
- return; // Wait till we *really* need to lock and load.
- // Acquire all we can from the inter-thread queue with one lock acquisition.
- {
- AutoLock lock(incoming_queue_lock_);
- if (incoming_queue_.empty())
- return;
- std::swap(incoming_queue_, work_queue_);
- DCHECK(incoming_queue_.empty());
- }
- }
关于 delayed_work_queue_ 和deferred_non_nestable_work_queue 队列后面再来介绍。
(2)消息动态流程
和WINDOWS的传统消息机制类似,但是它扩充了WINDOWS的消息循环机制,使得它能够支持事件对象和任务。
这张图片简单的描述了消息的线程的创建和通讯过程(看大图片)。
Chrome对自己的线程采取一种lazyCreate 的策略,就是每个线程在需要的时候才被创建。线程之间是通过g_browser_process这个全局对对象来获取相应的线程中的消息循环,然后再通过消息循环中的PostTask 系列的函数来发送任务到相应的线程队列中去。