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 optman : chrome 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