Chrome MessageLoop

本文深入剖析Chromium中消息循环的实现机制,涵盖MessageLoop、MessagePump等关键类的作用及工作流程,揭示不同线程如何高效处理自定义任务、IO操作及UI消息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

https://blog.youkuaiyun.com/ljm198745/article/details/40682951

https://blog.youkuaiyun.com/qq3401247010/article/details/78286506

 

 

https://blog.youkuaiyun.com/fingding/article/details/34088277

   Android 平台下:                                                       

                                                                   MessageLoop::Run

                                                                                |
                                                                MessagePump(this)::Run
                                                                                |
MessagePumpAndroid(UI线程)        MessagePumpLibevent(IO线程)         MessagePumpDefault(其他线程)
    (自定义task和UI消息)                           (自定义task和IO消息)                          (自定义task) 
                                                                                |
                                                                                |
                                                                              DoWork
                                                                              DoDelayedWork
                                                                              DoIdleWork




1. 自定义task
MessageLoop::PostTask(task)
     |
     | ---> incoming_queue_.push(task)   (自定义task queue)
     |
     | ---> message_loop_->ScheduleWork()   | ---> MessagePumpAndroid::ScheduleWork ---> Java_SystemMessageHandler_setTimer()
                                                                       | ---> MessagePumpLibevent::ScheduleWork ---> write(wakeup_pipe_in_, &buf, 1) 
                                                                       | ---> MessagePumpDefault::ScheduleWork ---> WaitableEvent.Signal()




2. render进程向browser进程发消息
bool RenderWidget::Send(IPC::Message* msg)
                    |
RenderThreadImpl::Send(IPC::Message* msg)
                    |
ChildThread::Send(IPC::Message* msg)
                    |
SyncChannel::Send(IPC::Message* msg)
                    |
ChannelProxy::Send(msg)



3. browser进程向render进程发消息
bool RenderWidgetHostImpl::Send(IPC::Message* msg)

                    |

bool RenderProcessHostImpl::Send(IPC::Message* msg)
                    |

IPC::ChannelProxy::Send(msg)
                    |
ipc_task_runner()->PostTask(ChannelProxy::Context::OnSendMessage)
                    |
 ChannelProxy::Context::OnSendMessage
                    |
bool Channel::ChannelImpl::Send(Message* message) 
                    |
                    | ---> output_queue_.push(message)
                    |
                    | ---> Channel::ChannelImpl::ProcessOutgoingMessages()  ---> write(pipe_, out_bytes, amt_to_write)

 

--------------------- 本文来自 fingding 的优快云 博客 ,全文地址请点击:https://blog.youkuaiyun.com/fingding/article/details/34088277?utm_source=copy 

 

http://www.360doc.com/content/13/0422/16/168576_280145531.shtml

转载请注明出处:http://blog.youkuaiyun.com/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

--------------------- 本文来自 Yongsheng 的优快云 博客 ,全文地址请点击:https://blog.youkuaiyun.com/milado_nju/article/details/8539795?utm_source=copy

 

 

Chrome MessageLoop类分析

2009年12月14日 18:12:00 optmanchrome windows delay thread io winapi

https://blog.youkuaiyun.com/optman/article/details/5005660

Windows程序是基于消息的,不管其封装形式如何,最后都要包含如下代码

MSG msg;
while(GetMesssage(&msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg); 
}


大部分的工作都是在这个while循环里完成。 GetMessage获得一条消息,然后调用DispatchMessage分发消息。DispatchMessage首先找到消息对应的窗口,调用窗口的消息处理函数,在消息处理函数里通过switch/case执行对应的处理代码,然后返回继续获得下一个消息GetMessage。直到收到WM_QUIT消息,while循环才会退出,否则一直阻塞在GetMessage处等待消息。




一条线程启动后,如果不是进入消息循环,那么很快就结束,比如以下线程入口函数


unsigned int WINAPI ThreadFunc(LPVOID param)
{
... 


//执行到这里,线程就准备退出了
}


因为创建线程是有代价的,我们希望线程能一直运行,所以加入消息循环,比如


unsigned int WINAPI ThreadFunc(LPVOID param)
{
... 


while(WaitForMessage()) 
{
DispatchMessage(); 
}

}


消息循环就是让线程一直运行在类似的while循环中,不断检查和分发消息,直到收到特定消息而退出循环,然后线程就结束了。 




一般来说,除非只为特定工作而创建的线程之外,通用的公共线程都应该有一个消息队列和消息循环。 这样,我们可以通过把消息发送到指定线程的消息队列中,从而让消息在指定线程中处理,而避免多线程访问所带来的并发访问问题。




在Chrome里,是使用MessageLoop对象来封装以上的消息处理循环代码。一条线程在启动后,就会创建MessageLoop对象,并调用MessageLoop.Run()方法来进入消息循环。当MessageLoop.Run()函数返回,即线程退出。


class MessageLoop : public base::MessagePump::Delegate {
{
public:


// Run the message loop.
void Run();


// Signals the Run method to return after it is done processing all pending
// messages. This method may only be called on the same thread that called
// Run, and Run must still be on the call stack.
//
// Use QuitTask if you need to Quit another thread's MessageLoop, but note
// that doing so is fairly dangerous if the target thread makes nested calls
// to MessageLoop::Run. The problem being that you won't know which nested
// run loop you are quiting, so be careful!
//
void Quit();


// Returns the MessageLoop object for the current thread, or null if none.
static MessageLoop* current();




// The "PostTask" family of methods call the task's Run method asynchronously
// from within a message loop at some point in the future.
//
// With the PostTask variant, tasks are invoked in FIFO order, inter-mixed
// with normal UI or IO event processing. 
//
// The MessageLoop takes ownership of the Task, and deletes it after it has
// been Run().
//
// NOTE: These methods may be called on any thread. The Task will be invoked
// on the thread that executes MessageLoop::Run().


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


调用MessageLoop::current()方法可以获得当前线程对应的MessageLoop对象,并可以调用其PostTask方法让该线程上执行一个Task。PostTask把Task放入队列后就返回了,Task会在稍后的消息循环中得到处理。 


MessageLoop除了执行Task,还可以处理常规的windows消息以及IO事件等,具体要看MessagLoop的类型。 MessageLoop内部使用使用MessagePump对象来处理消息,并根据不同的类型创建不同的MessagePump。




MessageLoop::MessageLoop(Type type)
{
lazy_tls_ptr.Pointer()->Set(this);


if (type_ == TYPE_DEFAULT) {
pump_ = new base::MessagePumpDefault();
} else if (type_ == TYPE_IO) {
pump_ = new base::MessagePumpForIO();
} else {
DCHECK(type_ == TYPE_UI);
pump_ = new base::MessagePumpForUI();
}
}


void MessageLoop::RunInternal() 
{
pump_->Run(this);
}


MessagePumpDefault最简单,只执行Task。MessagePumpForUI可以处理windows消息队列,而MessagePumpForIO可以处理IO完成端口的消息。不同Pump获得消息的方式是不一样的,MessagePumpDefault等待Event信号量,PumpForUI从Windows的消息队列,而PumpForIO是从IO完成端口。


void MessagePumpDefault::Run(Delegate* delegate) {

if (delayed_work_time_.is_null()) {
event_.Wait();
} else {
TimeDelta delay = delayed_work_time_ - Time::Now();
if (delay > TimeDelta()) {
event_.TimedWait(delay);
} else {
delayed_work_time_ = Time();
}
}
}


void MessagePumpForUI::WaitForWork() {


DWORD result;
result = MsgWaitForMultipleObjectsEx(0, NULL, delay, QS_ALLINPUT,
MWMO_INPUTAVAILABLE);


if (WAIT_OBJECT_0 == result) {
MSG msg = {0};
DWORD queue_status = GetQueueStatus(QS_MOUSE);
if (HIWORD(queue_status) & QS_MOUSE &&
!PeekMessage(&msg, NULL, WM_MOUSEFIRST, WM_MOUSELAST, PM_NOREMOVE)) {
WaitMessage();
}
return;
}




bool MessagePumpForIO::GetIOItem(DWORD timeout, IOItem* item) {
memset(item, 0, sizeof(*item));
ULONG_PTR key = NULL;
OVERLAPPED* overlapped = NULL;
if (!GetQueuedCompletionStatus(port_.Get(), &item->bytes_transfered, &key,
&overlapped, timeout)) {
if (!overlapped)
return false; // Nothing in the queue.
item->error = GetLastError();
item->bytes_transfered = 0;
}


item->handler = reinterpret_cast<IOHandler*>(key);
item->context = reinterpret_cast<IOContext*>(overlapped);
return true;
}




PostTask只是把Task放在一个Task队列里的,如果这时候线程处于等待消息的阻塞状态,那么还需要发送一条消息去唤醒线程。不同的Pump使用不同的方式,PumpDefault是等待Event信号量,PumpForUI是阻塞在GetMessage上等待Windows消息,所以可以发送Windows消息唤醒。而PumpForIO是阻塞在IO完成端口上,那么只要模拟发送一个IO完成信息过去即可唤醒线程。


void MessagePumpDefault::ScheduleWork() {
// Since this can be called on any thread, we need to ensure that our Run
// loop wakes up.
event_.Signal();
}


void MessagePumpForUI::ScheduleWork() {
PostMessage(message_hwnd_, kMsgHaveWork, reinterpret_cast<WPARAM>(this), 0);
}


void MessagePumpForIO::ScheduleWork() {
BOOL ret = PostQueuedCompletionStatus(port_, 0,
reinterpret_cast<ULONG_PTR>(this),
reinterpret_cast<OVERLAPPED*>(this));
}




MessagePump对Native消息(Windows消息或IO完成消息)有自己的处理方式(PumpDefault没有消息要处理),而Task则由MessageLoop来统一处理。


void MessagePumpDefault::Run(Delegate* delegate) {


for (;;) {
ScopedNSAutoreleasePool autorelease_pool;


bool did_work = delegate->DoWork();
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;
}


void MessagePumpForUI::DoRunLoop() {
for (;;) {
bool more_work_is_plausible = ProcessNextWindowsMessage();
if (state_->should_quit)
break;


if (more_work_is_plausible)
continue;


more_work_is_plausible = state_->delegate->DoIdleWork();
if (state_->should_quit)
break;


if (more_work_is_plausible)
continue;


WaitForWork(); // Wait (sleep) until we have work to do again.
}
}




void MessagePumpForIO::DoRunLoop() {
for (;;) {
bool more_work_is_plausible = state_->delegate->DoWork();
if (state_->should_quit)
break;


more_work_is_plausible |= WaitForIOCompletion(0, NULL);
if (state_->should_quit)
break;


if (more_work_is_plausible)
continue;


more_work_is_plausible = state_->delegate->DoIdleWork();
if (state_->should_quit)
break;


if (more_work_is_plausible)
continue;


WaitForWork(); // Wait (sleep) until we have work to do again.
}
}


MessageLoop对象从队列里取出Task执行,即调用Task::Run,然后删除。


void MessageLoop::RunTask(Task* task) {
task->Run();
delete task;
}




当一条线程创建了MessageLoop对象之后,它就具备了运行Task的能力,我们就可以任意指派Task在该线程执行。就像这样


some_message_loop->PostTask( some_task);




因为MessageLoop的本意是处理和分发消息的,是共用,所以消息处理代码不宜运行过长时间,包括Task。因为这会长时间阻塞住整条MessageLoop,影响其它消息的处理。对于需要长时间运行的,还是需要创建单独的工作线程,或者调用异步执行代码。 比如Socket, Pipe和File都是可以用非阻塞的方式操作,即异步IO--在Windows平台上可以使用IO完成端口来方便处理。MessageLoopForIO可以用于异步IO专用线程,调用者只有把IO对象的句柄交给MessageLoopForIO对象(MessagePumpForIO ::RegisterIOHandler),以后每当有IO对象的操作完成时,调用者就会从MessageLoopForIO收到回调通知(IOHandler::OnIOCompleted)。 MessageLoopForUI就更不用说了,因为Windows窗口是绑定到创建线程上的,所以只要有一个MessageLoopForUI对象就可以处理属于该线程的所有Windows消息,不需要额外的注册过程。 


class MessagePumpForIO : public MessagePumpWin {


class IOHandler {
public:
virtual ~IOHandler() {}
// This will be called once the pending IO operation associated with
// |context| completes. |error| is the Win32 error code of the IO operation
// (ERROR_SUCCESS if there was no error). |bytes_transfered| will be zero
// on error.
virtual void OnIOCompleted(IOContext* context, DWORD bytes_transfered,
DWORD error) = 0;
};

void RegisterIOHandler(HANDLE file_handle, IOHandler* handler);


}


所以,如果相关模块都可以异步调用,那么只要有两条线程(分别处理UI和IO消息)即可满足程序的需要,而主线程一般就是UI线程。 使用基于MessageLoop的消息分发机制,可以大大减少线程的数量,避免程序并发带来的各种问题。


在线源码:
http://src.chromium.org/viewvc/chrome/trunk/src/base/message_loop.h?revision=30066
http://src.chromium.org/viewvc/chrome/trunk/src/base/message_loop.cc?revision=30066
http://src.chromium.org/viewvc/chrome/trunk/src/base/message_pump.h?revision=10791
http://src.chromium.org/viewvc/chrome/trunk/src/base/message_pump_default.h?revision=4022
http://src.chromium.org/viewvc/chrome/trunk/src/base/message_pump_default.cc?revision=4022
http://src.chromium.org/viewvc/chrome/trunk/src/base/message_pump_win.h?revision=5021
http://src.chromium.org/viewvc/chrome/trunk/src/base/message_pump_win.cc?revision=14649

 

 MessageLoop

 

 

从chromium的消息循环的实现方式中,总结起来会发现有以下几个优点:

1、机制简单。chromium处理消息只有交换队列时才会有加锁,任务都是封装成task交到线程队列中,从而避免多线程任务可能存在的同步、异步等许多问题。简单来说A线程需要B线程做一些事情,然后回到A线程继续做一些事情;在Chrome下你可以这样来实现:生成一个Task,放到B线程的队列中,在该Task的Run方法最后,会生成另一个Task,这个Task会放回到A的线程队列,由A来执行。

2、分工明确,不同的线程处理不同性质的任务,UI线程处理窗口信号,IO线程处理IO事件,其它线程各自处理自己的任务,使线程能更快响应,避免UI线程被IO事件卡住这类问题。

 

--------------------- 本文来自 正版风之彼岸 的优快云 博客 ,全文地址请点击:https://blog.youkuaiyun.com/ljm198745/article/details/40682951?utm_source=copy

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值