[Chrome源码阅读] MessageLoop类的点点滴滴

原创 2012年08月24日 18:28:02
一个线程只能有一个MessageLoop被实例化。
在BrowserMain函数中(这个函数是browser进程的入口函数),实例化了一个局部变量MessageLoop:
MessageLoop main_message_loop ( MessageLoop:: TYPE_UI );
之后就可以直接调用的MessageLoop的静态函数current()获取那个messageloop:
MessageLoop ::current ()-> Run();
这其中的技术就是线程局部存储。当构造一个messageloop是,构造函数会将这个messageloop设置到这个线程的一个局部变量中,之后每次current()调用就是获取这个局部变量的指针。注意的是它只是存储一个局部变量的指针,如果对象超出作用域销毁了,那么这个TLS中存储的就是一个野指针。可幸的是chrome为我们做足了功夫,在messageloop销毁时,析构函数就会重置这个TLS。

下面来看MessageLoop与MessagePump之间的相互关系:

MessageLoop实现了接口MessagePump::Delegate,实现了3个处理不同类型TASK的虚函数。同时MessagePump也是一个接口类,跟平台相关的类MessagePumpWin并没有实现它的接口,而是2个特别的子类进行了实现。MessageLoop类是对外的接口,客户端代码一般不接触MessagePump。当实例化一个MessageLoop时就会实例化一个MessagePump,在Windows平台上是MessagePumpWin,针对不同的事务处理类型,会有三个不同的继承类:MessagePumpForUI,MessagePumpForIO,和MessagePumpDefault。MessagePump*类是真正干活的,MessageLoop::Run函数转调用pump_::Run函数(MessagePump),并将自己作为MessagePump::Delegate传进去。
MessagePump::Run函数处理流程会按照如下的方式进行:
 for (;;) {
       bool did_work = DoInternalWork();
       if (should_quit_)
         break;
  
       did_work |= delegate_->DoWork();
       if (should_quit_)
         break;
  
       did_work |= delegate_->DoDelayedWork();
       if (should_quit_)
         break;
  
       if (did_work)
         continue;
  
       did_work = delegate_->DoIdleWork();
       if (should_quit_)
         break;
  
       if (did_work)
         continue;
  
       WaitForWork();
     }
从以上的流程可知,我们总是优先处理完NON-IDEL TASK,实在没有NON-IDEL TASK了,才处理IDEL TASK。
不同类型的MessagePump会有不同的处理,例如Default类型只处理Task和Timer,那么它就没有UI类型需要处理Windows的消息事件,譬如下面的代码段:(MessagePumpDefault类定义在文件\src\base\message_pump_default.h,其它2个类定义在\src\base\message_pump_win.h)。
void MessagePumpDefault::Run(Delegate* delegate) {
  DCHECK(keep_running_) << "Quit must have been called outside of Run!";

  for (;;) {
    ScopedNSAutoreleasePool autorelease_pool;

    bool did_work = delegate->DoWork();
    if (!keep_running_)
      break;

    did_work |= delegate->DoDelayedWork(&delayed_work_time_);
    if (!keep_running_)
      break;

    if (did_work)
      continue;

    did_work = delegate->DoIdleWork();
    if (!keep_running_)
      break;

    if (did_work)
      continue;

    if (delayed_work_time_.is_null()) {
      event_.Wait();
    } else {
      TimeDelta delay = delayed_work_time_ - Time::Now();
      if (delay > TimeDelta()) {
        event_.TimedWait(delay);
      } else {
        // It looks like delayed_work_time_ indicates a time in the past, so we
        // need to call DoDelayedWork now.
        delayed_work_time_ = Time();
      }
    }
    // Since event_ is auto-reset, we don't need to do anything special here
    // other than service each delegate method.
  }

  keep_running_ = true;
}
我们前篇文章讲到,MessageLoop是在新的线程的栈上构造出来的,所以它的Run函数进而MessagePump::Run函数都是运行在那个新开辟的线程中的。

前面提到MessageLoop是对外的接口类,主要是因为它提供了PostTask族接口函数,这类函数方便客户端将不同类型的TASK放到当前或者其它线程的消息队列里。说是当前,是因为你可以这样调用:MessageLoop::current()->PostTask(...),MessageLoop::current()函数返回当前线程的message loop。如果想要另外一个线程帮忙执行某个TASK,而且也知道那个线程,那么可以这样调用SomeThread->message_loop()->PostTask(...)。在UI线程里通过全局变量g_browser_process可以方便的拿到其它“有名”线程地址,然后用前面的方式请求执行某一个TASK。
4个PostTask函数接口如下:
  void PostTask(
      const tracked_objects::Location& from_here, Task* task);

  void PostDelayedTask(
      const tracked_objects::Location& from_here, Task* task, int delay_ms);

  void PostNonNestableTask(
      const tracked_objects::Location& from_here, Task* task);

  void PostNonNestableDelayedTask(
      const tracked_objects::Location& from_here, Task* task, int delay_ms);
New出来一个TASK,这个TASK的生命期就有MessageLoop来管理,在运行完那个TASK之后,就会被销毁。4个函数都会统一转调用一个内部的函数:PostTask_Helper。我们来看下这个PostTask_Help函数干了什么活:
void MessageLoop::PostTask_Helper(
    const tracked_objects::Location& from_here, Task* task, int delay_ms,
    bool nestable) {
  task->SetBirthPlace(from_here);

  PendingTask pending_task(task, nestable);

  if (delay_ms > 0) {
    pending_task.delayed_run_time =
        Time::Now() + TimeDelta::FromMilliseconds(delay_ms);
  } else {
    DCHECK(delay_ms == 0) << "delay should not be negative";
  }

  // Warning: Don't try to short-circuit, and handle this thread's tasks more
  // directly, as it could starve handling of foreign threads.  Put every task
  // into this queue.

  scoped_refptr<base::MessagePump> pump;
  {
    AutoLock locked(incoming_queue_lock_);

    bool was_empty = incoming_queue_.empty();
    incoming_queue_.push(pending_task);
    if (!was_empty)
      return;  // Someone else should have started the sub-pump.

    pump = pump_;
  }
  // Since the incoming_queue_ may contain a task that destroys this message
  // loop, we cannot exit incoming_queue_lock_ until we are done with |this|.
  // We use a stack-based reference to the message pump so that we can call
  // ScheduleWork outside of incoming_queue_lock_.

  pump->ScheduleWork();
}
可以看到它首先实例化一个PendingTask(PendingTask仅仅是对Task*进行了简单的封装,并且额外添加了一些控制参数),然后把它Enqueue到incoming_queue_队列中,注意这里enqueue时用到了lock(这么久了,好像很少用到锁,这里是一例)。如果以前队列已经有TASK,直接返回(不需要发信号给线程,注意PostTask函数是在调用线程中执行的),否则需要调用ScheduleWork来唤醒那个线程(因为没有TASK,线程就阻塞在那个信号量上)。
从COMMENT知道,代码将pump_赋值给另一个变量,用来增加引用计数,让lock可以快速释放,从而避免了一种CASE?
注意这里是将PendingTask放到incomming_queue_队列中,其实MessageLoop还以另外一个TASK队列叫做work_queue_,所以说incomming_queue_只是一个缓存队列,当work_queue_为空时,才会将两者进行交换(有点类似于OPENGL的双显示缓存机制),交换前需要锁住incomming_queue_队列。这是MessageLoop::ReloadWorkQueue函数干的事情。


Chrome MessageLoop类分析

Windows程序是基于消息的,不管其封装形式如何,最后都要包含如下代码MSG msg;while(GetMesssage(&msg)){TranslateMessage(&msg);Dispatch...
  • optman
  • optman
  • 2009年12月14日 18:12
  • 3525

[Chromium]如何安全的使用PostTask

一般场景决策树如何传递绑定的对象官方的解释总是最权威,有疑问看这里或者直接看代码中的说明: bind_helpers.h. 传值方式描述示例this 或 对象指针如果对象本身是一个RefCounted...
  • HorkyChen
  • HorkyChen
  • 2016年01月09日 01:25
  • 3206

Chrom 的线程模型

线程(http://www.chromium.org/developers/design-documents/threading ) a) 概述 Chromium是一个超级多线程的产品,我们尝试让...
  • dylgsy
  • dylgsy
  • 2012年08月08日 21:43
  • 4983

chromium源码阅读--进程的Message Loop

消息处理是由base::MessageLoop中实现,消息中的任务和定时器都是异步事件的。 主要如下几点: 1、消息的类型分类 2、延时处理的消息是如何实现   一、消息...
  • qq3401247010
  • qq3401247010
  • 2017年10月19日 16:33
  • 123

深入剖析WTL—WTL消息循环机制详解

WTL消息循环机制实现了消息过滤和空闲处理机制。 消息过滤 首先看一下CMessageLoop的核心逻辑CMessageLoop.Run()的代码: int CMessageLoop.Run()...
  • zcxin
  • zcxin
  • 2013年10月23日 00:15
  • 1246

Chrome Task类分析

在上一篇《Chrome线程模型》之后,我们来实际看一看代码。多线程编程完全基于消息传递会比较麻烦,因为消息的封装和解析是比较麻烦的。不仅如此,被多个线程调用的其实是同一个对象的不同方法。比如class...
  • optman
  • optman
  • 2009年12月09日 20:30
  • 3589

MFC使用CEF并实现js与C++交互功能,解决Render进程中OnContextCreated绑定与OnWebKitInitialized的js扩展无法回调问题

MFC使用CEF并实现js与C++交互功能,解决Render进程中OnContextCreated绑定与OnWebKitInitialized的js扩展无法回调问题...
  • chenlycly
  • chenlycly
  • 2016年11月26日 22:13
  • 3290

Cef功能开发经验总结

这是我开发Cef功能时对踩过的坑,进行的总结,话说Cef坑真的不少。好在踩完后用起来还是挺爽的。最终的代码可以下载网易云信PC Demo点我跳转 资料准备 这是我集成过程中查到的一些资料,包括了C...
  • i7thTool
  • i7thTool
  • 2017年04月14日 12:54
  • 8652

进公司的点点滴滴

2015年2月2日,我进了悦厚科技,同时也是我们部门的第一女生,刚进公司那会,感觉他们都很好,老大帮我弄好了所有的东西,而安静帮我安装了Google,而胡哥却是很低调的。到了中午他们一起带我去吃饭,饭...
  • nlwx520
  • nlwx520
  • 2016年03月30日 17:39
  • 253

大学生活的点滴感悟

仅以此文jilu
  • wqztmx4
  • wqztmx4
  • 2014年05月18日 00:20
  • 613
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:[Chrome源码阅读] MessageLoop类的点点滴滴
举报原因:
原因补充:

(最多只允许输入30个字)