Chromium的线程模型和消息循环机制一直是其很有亮点也非常值得学习的一部分,关于这部分的工作原理网上有很多好文章,比如这篇、这篇还有这篇。但是这几篇文章基本是站在一定的高度上来概括其思想,而深入到代码里还有一些流程及细节问题,在此略做总结。
首先,在线程启动时基本会按以下流程来启动消息循环:
#1base::MessagePumpLibevent::Run base/message_pump_libevent.cc:243
#2MessageLoop::RunInternal base/message_loop.cc:419
#3MessageLoop::RunHandler base/message_loop.cc:392
#4base::RunLoop::Run base/run_loop.cc:45
#5MessageLoop::Run base/message_loop.cc:299
#6base::Thread::Run base/threading/thread.cc:133
#7content::BrowserThreadImpl::IOThreadRun content/browser/browser_thread_impl.cc:149
#8content::BrowserThreadImpl::Run content/browser/browser_thread_impl.cc:177
#9base::Thread::ThreadMain base/threading/thread.cc:169
#10ThreadFunc base/threading/platform_thread_posix.cc:65
#11__thread_entry ()
#12pthread_create ()
这是Browser进程的IO线程启动时的调用堆栈,其他线程的启动也与此类似(UI线程除外)。可以看到,实际的消息循环是在MessagePump类的run函数中进行的,如果有任务则执行任务,没有任务则在此run函数中等待。
UI线程是比较特殊的,它是主线程,需要处理各种与用户的交互,所以它是不能阻塞,且与平台相关的。在各个平台下UI线程的MessagePumpForUI类的实现是不同的,它也不使用MessageLoop提供的消息循环,而是使用平台提供的消息循环。比如chromiumfor android使用的是android提供的handler那一套。
MessageLoop类主要维持循环中的各个队列,而真正处理各种等待、唤醒、阻塞和处理事件的则是在MessagePump中进行的。这里用到了一个第三方库libevent来进行事件注册、阻塞、唤醒等操作。
MessagePump在初始化时(init函数)会注册事件,然后在run函数循环过程中会等待该事件。其中MessagePumpDefault最简单,它只能处理Task任务,所以它run时直接阻塞在一个条件变量上即可,然后等待ScheduleWork函数来唤醒它。ScheduleWork什么时候调用呢?已经很明确了,在有人调用MessageLoop::AddToIncomingQueue向该线程的消息incoming队列中放任务时,就要调用MessagePumpDefault的ScheduleWork函数。
MessagePumpLibevent类的处理就比较复杂了,它除了处理Task任务,还要处理IPC消息,系统消息等。这时需要同时监听多个事件,就不能直接用条件变量了。虽然是用libevent库实现的,但底层应该是用类似select系统调用,来同时监听多个文件描述符,来达到监听多个事件的目的。
其中Task任务会阻塞在一个管道的read函数上,在init函数中创建的该管道,然后在OnWakeup函数read,如果管道中没有数据在这里会阻塞住。在ScheduleWork函数中会像管道中写数据以唤醒OnWakeup。至于ScheduleWork怎么调用的就不用多说了吧。
而对IPC消息的监听会在WatchFileDescriptor函数注册需要监听的套接字,而该函数是在channel类的AcceptConnect函数调用过来的,具体流程可参见这里。若是套接字有数据到达,触发事件后会调用OnLibeventNotification函数来处理IPC消息,关于IPC消息的处理流程还是参见这里。
MessagePumpForUI类前面已经说过,使用系统提供的消息循环。也就是说其run函数其实没有作用,而ScheduleWork也会转发到系统消息循环。至于其机制,可参见android异步通信机制。
至此chromium的消息循环机制的流程就大体上有了,但实际中更为复杂一些,比如任务也分为PostTask,PostDelayedTask,PostNonNestableTask,PostNonNestableDelayedTask几种,相应的任务队列也分为多个。
关于任务是怎么封装的又是另外一个值得学习的地方,以后有时间继续总结。