chromium中的消息循环

这篇文章会更加深入地讲解chromium的线程,通常为了保证ui的响应速度,防止io阻塞和复杂计算卡死ui,会把io任务和复杂计算的任务放到其它线程中运行。由于反复创建线程会增加系统负担,在设计的时候通常不会把任务交给线程,在线程运行完成后直接退出线程。chromium中每一个线程都会有一个消息循环MessageLoop,接收来自其它线程的任务,在处理完成后接着处理下一个任务,当没有任务时,挂起线程,等待任务。MessageLoop的类图如下:

根据MessageLoop的类型,在MessageLoop中会实例化不同的MessagePump,在windows环境下,有三种MessagePump,分别是MessagePumpDefault、MessagePumpForUI、MessagePumpForIO。其中MessagePumpDefault只处理Task,MessagePumpForUI会处理windows消息和Task,MessagePumpForIO会处理IO事件和Task。由于在没有计算任务或者等待windows消息和io事件时,线程会挂起,为了使计算任务不会在线程挂起时会“饿死”,MessagePump中还定义了ScheduleWork函数,在有计算任务的时候进行调用,使得计算任务能及时调度。

MessagePump的Run函数是执行消息循环的过程,不同的MessagePump的实现如下

void MessagePumpDefault::Run(Delegate* delegate) {
  for (;;) {
    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;
    ThreadRestrictions::ScopedAllowWait allow_wait;
    if (delayed_work_time_.is_null()) {
      event_.Wait();
    } else {
      TimeDelta delay = delayed_work_time_ - TimeTicks::Now();
      if (delay > TimeDelta()) {
        event_.TimedWait(delay);
      } else {
        delayed_work_time_ = TimeTicks();
      }
    }
  }
  keep_running_ = true;
}
void MessagePumpForUI::DoRunLoop() {
  for (;;) {
    bool more_work_is_plausible = ProcessNextWindowsMessage();
    if (state_->should_quit)
      break;
    more_work_is_plausible |= state_->delegate->DoWork();
    if (state_->should_quit)
      break;
    more_work_is_plausible |=
        state_->delegate->DoDelayedWork(&delayed_work_time_);
    if (more_work_is_plausible && delayed_work_time_.is_null())
      KillTimer(message_hwnd_, reinterpret_cast<UINT_PTR>(this));
    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;
    more_work_is_plausible |=
        state_->delegate->DoDelayedWork(&delayed_work_time_);
    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.
  }
}

主循环中使用到的delegate就是MessageLoop,因为它实现了MessagePump::Delegate接口。主循环中不同的地方只是等待任务的方式有些区别,其它的部分就是调用MessageLoop的DoWork()、DoDelayedWork()、DoIdleWork()函数。由于不同的MessagePump处理的任务有些区别,它们的等待机制实现也有所不同,唤醒线程的方式也有所区别。

(1)MessagePumpDefault等待消息使用信号量event_,如果没有延时任务,则进入无限期等待,如果有延时任务,等待相应的延时后唤醒。它唤醒线程的方式也很简单,只需要对信号量进行设置。

 

//等待消息
if (delayed_work_time_.is_null()) {
  event_.Wait();
} else {
  TimeDelta delay = delayed_work_time_ - TimeTicks::Now();
  if (delay > TimeDelta()) {
    event_.TimedWait(delay);
  } else {
    delayed_work_time_ = TimeTicks();
  }
}
//唤醒线程
void MessagePumpDefault::ScheduleWork() {
  event_.Signal();
}
(2)MessagePumpForUI调用MsgWaitForMultipleObjectsEx等待系统的信号量,这个函数是阻塞的。当检查到有信号量时,返回。但有一种情况比较特别,就是存在父子关系的窗口关系,当一个线程中调用MsgWaitForMultipleObjectsEx检查到有消息后,另一个线程可能已经调用PeekMessage把消息取走,这时候PeekMessage会取不到消息,导致WaitForWork返回,程序运行一个空循环,像鼠标事情这种比较频繁的信号,多次运行空循环会增加系统cpu负担,这时候当检查到队列中有鼠标消息但是PeekMessage又取不到,就会进入等待(这部份代码的意图我没有完全吃透,到以后对windows编程理解更深后再回来看看)。

MessagePumpForUI通过往窗口消息队列中加入一条类型为kMsgHaveWork的消息来实现,have_work这个标记是用来保证窗口消息队列中只能有一条kMsgHaveWork消息,InterlockedExchange这个函数是把变量值设置成第二个参数值,并返回原始值,它是一个原子操作,可以理解成是加锁的,线程安全。可以发现当消息队列中如果有kMsgHaveWork消息,也就是have_work的值为1时,ScheduleWork会直接返回,否则会调用PostMessage往消息队列中加入一条MsgHaveWork消息,WaitForWork等待到消息时,会唤醒线程。

void MessagePumpForUI::WaitForWork() {
  int delay = GetCurrentDelay();
  if (delay < 0)  // Negative value means no timers waiting.
    delay = INFINITE;
  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;
  }
}
void MessagePumpForUI::ScheduleWork() {
  if (InterlockedExchange(&have_work_, 1))
    return;  // Someone else continued the pumping.
  BOOL ret = PostMessage(message_hwnd_, kMsgHaveWork,
                         reinterpret_cast<WPARAM>(this), 0);
  if (ret)
    return;  // There was room in the Window Message queue.
  InterlockedExchange(&have_work_, 0);  // Clarify that we didn't really insert.
}

(3)MessagePumpForIO使用的是windows完成端口模型,它的WaitForWork 逻辑也比较复杂,WaitForIOCompletion中的filter参数在代码逻辑里都是NULL,所以completed_io_通常都是空。GetIOItem中GetQueuedCompletionStatus检查队列中是否有I/O处理完成的结果。在完成端口模型中,当一个设备的异步I/O请求完成之后,系统会检查该设备是否关联了一个完成端口,如果是,系统就向该完成端口的I/O完成队列中加入完成的I/O请求列。GetQueuedCompletionStatus使调用线程挂起,直到指定的端口的I/O完成队列中出现了一项或直到超时。IOItem会保存传输的字节数,句柄和Overlapped结构地址,因为句柄handler是一个指针,是4字节对齐,所以handler后两位一定是0,在实现时会把handler和has_valid_io_context 的合并成一个key,在关联完成端口时做为参数输入。GetIOItem返回后,会调用ProcessInternalIOItem,判断是否是ScheduleWork中伪造的IO完成事件,如果不是调用item.handler->OnIOCompleted接口,处理IO完成后的数据。

MessagePumpForIO的ScheduleWork的实现和MessagePumpForUI很相似,当有任务需要调度时,会伪造一个IO完成事件,放入到完成队列中,使GetQueuedCompletionStatus返回,唤醒线程。它同样有一个标记位have_work_保证完成队列中最多有一个伪造的IO完成事件。

void MessagePumpForIO::WaitForWork() {
  int timeout = GetCurrentDelay();
  if (timeout < 0)  // Negative value means no timers waiting.
    timeout = INFINITE;
  WaitForIOCompletion(timeout, NULL);
}

bool MessagePumpForIO::WaitForIOCompletion(DWORD timeout, IOHandler* filter) {
  IOItem item;
  if (completed_io_.empty() || !MatchCompletedIOItem(filter, &item)) {
    if (!GetIOItem(timeout, &item))
      return false;
    if (ProcessInternalIOItem(item))
      return true;
  }
  if (!item.has_valid_io_context || item.context->handler) {
    if (filter && item.handler != filter) {
      completed_io_.push_back(item);
    } else {
      DCHECK(!item.has_valid_io_context ||
             (item.context->handler == item.handler));
      WillProcessIOEvent();
      item.handler->OnIOCompleted(item.context, item.bytes_transfered,
                                  item.error);
      DidProcessIOEvent();
    }
  } else {
    delete item.context;
  }
  return true;
}
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 = KeyToHandler(key, &item->has_valid_io_context);
  item->context = reinterpret_cast<IOContext*>(overlapped);
  return true;
}

bool MessagePumpForIO::ProcessInternalIOItem(const IOItem& item) {
  if (this == reinterpret_cast<MessagePumpForIO*>(item.context) &&
      this == reinterpret_cast<MessagePumpForIO*>(item.handler)) {
    InterlockedExchange(&have_work_, 0);
    return true;
  }
  return false;
}
// Returns a completion item that was previously received.
bool MessagePumpForIO::MatchCompletedIOItem(IOHandler* filter, IOItem* item) {
  DCHECK(!completed_io_.empty());
  for (std::list<IOItem>::iterator it = completed_io_.begin();
       it != completed_io_.end(); ++it) {
    if (!filter || it->handler == filter) {
      *item = *it;
      completed_io_.erase(it);
      return true;
    }
  }
  return false;
}
// static
ULONG_PTR MessagePumpForIO::HandlerToKey(IOHandler* handler,
                                         bool has_valid_io_context) {
  ULONG_PTR key = reinterpret_cast<ULONG_PTR>(handler);
  if (!has_valid_io_context)
    key = key | 1;
  return key;
}

// static
MessagePumpForIO::IOHandler* MessagePumpForIO::KeyToHandler(
    ULONG_PTR key,
    bool* has_valid_io_context) {
  *has_valid_io_context = ((key & 1) == 0);
  return reinterpret_cast<IOHandler*>(key & ~static_cast<ULONG_PTR>(1));
}

void MessagePumpForIO::ScheduleWork() {
  if (InterlockedExchange(&have_work_, 1))
    return;  // Someone else continued the pumping.
  BOOL ret = PostQueuedCompletionStatus(port_, 0,
                                        reinterpret_cast<ULONG_PTR>(this),
                                        reinterpret_cast<OVERLAPPED*>(this));
  if (ret)
    return;  // Post worked perfectly.
  InterlockedExchange(&have_work_, 0);  // Clarify that we didn't succeed.
  UMA_HISTOGRAM_ENUMERATION("Chrome.MessageLoopProblem", COMPLETION_POST_ERROR,
                            MESSAGE_LOOP_PROBLEM_MAX);
}
在描述完消息循环怎么接收消息后,接下来需要说的就是处理消息的过程。首先最基础的处理就是task的处理,它是在MessageLoop中完成的,MessageLoop实现了 MessagePump::Delegate接口,它有三个主要的函数 DoWork()、DoDelayedWork()、DoIdleWork()。在 DoWork的化码中,ReloadWorkQueue的作用是把work_queue_与incoming_task_queue_交换,因为在PostTask的函数中,新加的任务都先保存在incoming_task_queue_,在DoWork中才对任务置换到work_queue_中进行处理。对于延时任务,会把任务放到delayed_work_queue_中,delayed_work_queue_是用优先队列实现的,它会把最先到期的时间通过ScheduleDelayedWork函数告诉MessagePump,使任务能及时唤醒。DeferOrRunPendingTask代码中,run_loop_->run_depth_ == 1通常是true,因为没有发现存在嵌套的循环,所以这个函数可以理解成直接运行任务RunTask(pending_task)。DoDelayedWork就是从delayed_work_queue_中取出延时任务,由于它是一个优先队列,队列的top()就是最先到期的延时任务,如果它的延时时间没有到就直接返回,如果时间到了就从队列中取出top(),运行延时任务。在MessageLoop中,并没有在栈上嵌套调用RunLoop::Run方式(因为RunHandler并没有RunLoop的实例),所以deferred_non_nestable_work_queue_应该是不会填充的,DoIdleWork中的ProcessNextDelayedNonNestableTask通常是返回false,但是并不是说DoIdleWork这个函数一定用也没有,如果MessageLoop使用的是RunUntilIdle接口运行的,会把quit_when_idle_received_置成true,表示线程空闲的时候就退出了,那么当运行到DoIdleWork时,调用  MessagePump::quit接口退出函数。

bool MessageLoop::DoWork() {
  if (!nestable_tasks_allowed_) {
    return false;
  }
  for (;;) {
    ReloadWorkQueue();
    if (work_queue_.empty())
      break;
    do {
      PendingTask pending_task = work_queue_.front();
      work_queue_.pop();
      if (!pending_task.delayed_run_time.is_null()) {
        AddToDelayedWorkQueue(pending_task);
        // If we changed the topmost task, then it is time to reschedule.
        if (delayed_work_queue_.top().task.Equals(pending_task.task))
          pump_->ScheduleDelayedWork(pending_task.delayed_run_time);
        } else {
          if (DeferOrRunPendingTask(pending_task))
            return true;
        }
    } while (!work_queue_.empty());
  }
  return false;
}
bool MessageLoop::DoDelayedWork(TimeTicks* next_delayed_work_time) {
  if (!nestable_tasks_allowed_ || delayed_work_queue_.empty()) {
    recent_time_ = *next_delayed_work_time = TimeTicks();
    return false;
  }
  TimeTicks next_run_time = delayed_work_queue_.top().delayed_run_time;
  if (next_run_time > recent_time_) {
    recent_time_ = TimeTicks::Now();  // Get a better view of Now();
    if (next_run_time > recent_time_) {
      *next_delayed_work_time = next_run_time;
      return false;
    }
  }
  PendingTask pending_task = delayed_work_queue_.top();
  delayed_work_queue_.pop();
  if (!delayed_work_queue_.empty())
    *next_delayed_work_time = delayed_work_queue_.top().delayed_run_time;
  return DeferOrRunPendingTask(pending_task);
}
bool MessageLoop::DoIdleWork() {
  if (ProcessNextDelayedNonNestableTask())
    return true;
  if (run_loop_->quit_when_idle_received_)
    pump_->Quit();
  return false;
}
bool MessageLoop::DeferOrRunPendingTask(const PendingTask& pending_task) {
  if (pending_task.nestable || run_loop_->run_depth_ == 1) {
    RunTask(pending_task);
    return true;
  }
  deferred_non_nestable_work_queue_.push(pending_task);
  return false;
}
bool MessageLoop::ProcessNextDelayedNonNestableTask() {
  if (run_loop_->run_depth_ != 1)
    return false;
  if (deferred_non_nestable_work_queue_.empty())
    return false;
  PendingTask pending_task = deferred_non_nestable_work_queue_.front();
  deferred_non_nestable_work_queue_.pop();
  RunTask(pending_task);
  return true;
}

MessagePumpForUI主要处理的消息是窗口消息,在ProcessNextWindowsMessage中,如果通过PeekMessage获取到消息,则进入处理消息的过程ProcessMessageHelper。如果消息是退出消息,调用PostQuitMessage处理退出事件,这个比较好理解。对于kMsgHaveWork,在上文中我们提到这是MessagePumpForUI自己增加的消息,这个消息是不会通过窗口函数WndProcThunk处理的,这时会尝试着重新取出一条消息进行处理,如果取到消息,就再创建一条kMsgHaveWork放到消息队列中,表示后续可能还有任务,防止WaitForWork阻塞线程(我原本以为这里代码的意思是处理一条窗口消息再处理一个task,可是后来我发现kMsgHaveWork消息不会被Dispatch到WndProcThunk进行处理,也就是WndProcThunk函数中处理kMsgHaveWork的逻辑是无效的,kMsgHaveWork在这里的作用只是为了防止WaitForWork阻塞线程,这个是我个人的看法,如果看官有自己的想法,欢迎留言)。对于其它的消息,则可能会对消息进行分发,或者是调用DispatchMessage到窗口函数WndProcThunk进行处理。
bool MessagePumpForUI::ProcessNextWindowsMessage() {
  bool sent_messages_in_queue = false;
  DWORD queue_status = GetQueueStatus(QS_SENDMESSAGE);
  if (HIWORD(queue_status) & QS_SENDMESSAGE)
    sent_messages_in_queue = true;
  MSG msg;
  if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) != FALSE)
    return ProcessMessageHelper(msg);
  return sent_messages_in_queue;
}

bool MessagePumpForUI::ProcessMessageHelper(const MSG& msg) {
  TRACE_EVENT1("base", "MessagePumpForUI::ProcessMessageHelper",
               "message", msg.message);
  if (WM_QUIT == msg.message) {
    state_->should_quit = true;
    PostQuitMessage(static_cast<int>(msg.wParam));
    return false;
  }
  if (msg.message == kMsgHaveWork && msg.hwnd == message_hwnd_)
    return ProcessPumpReplacementMessage();
  if (CallMsgFilter(const_cast<MSG*>(&msg), kMessageFilterCode))
    return true;
  WillProcessMessage(msg);
  uint32_t action = MessagePumpDispatcher::POST_DISPATCH_PERFORM_DEFAULT;
  if (state_->dispatcher)
    action = state_->dispatcher->Dispatch(msg);
  if (action & MessagePumpDispatcher::POST_DISPATCH_QUIT_LOOP)
    state_->should_quit = true;
  if (action & MessagePumpDispatcher::POST_DISPATCH_PERFORM_DEFAULT) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
  DidProcessMessage(msg);
  return true;
}

bool MessagePumpForUI::ProcessPumpReplacementMessage() {
  bool have_message = false;
  MSG msg;
  if (MessageLoop::current()->os_modal_loop()) {
    // We only peek out WM_PAINT and WM_TIMER here for reasons mentioned above.
    have_message = PeekMessage(&msg, NULL, WM_PAINT, WM_PAINT, PM_REMOVE) ||
                   PeekMessage(&msg, NULL, WM_TIMER, WM_TIMER, PM_REMOVE);
  } else {
    have_message = PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) != FALSE;
  }
  int old_have_work = InterlockedExchange(&have_work_, 0);
  if (!have_message)
    return false;
  ScheduleWork();
  return ProcessMessageHelper(msg);
}

使用MessagePumpForIO处理IO任务时需要把IO事件句柄和完成处理方式IOHandler注册到完成端口port_上,当有IO对象的操作完成时,MessageLoopForIO会调用IOHandler::OnIOCompleted方法处理IO完成事件。

void MessagePumpForIO::RegisterIOHandler(HANDLE file_handle,
                                         IOHandler* handler) {
  ULONG_PTR key = HandlerToKey(handler, true);
  HANDLE port = CreateIoCompletionPort(file_handle, port_, key, 1);
  DPCHECK(port);
}
bool MessagePumpForIO::RegisterJobObject(HANDLE job_handle,
                                         IOHandler* handler) {
  ULONG_PTR key = HandlerToKey(handler, false);
  JOBOBJECT_ASSOCIATE_COMPLETION_PORT info;
  info.CompletionKey = reinterpret_cast<void*>(key);
  info.CompletionPort = port_;
  return SetInformationJobObject(job_handle,
                                 JobObjectAssociateCompletionPortInformation,
                                 &info,
                                 sizeof(info)) != FALSE;
}

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

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

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




  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值