理解WebKit和Chromium: Chromium的多線程機制

轉載請註明出處:http://blog.csdn.net/milado_nju/

# Chromium多線程機制

## 概述

前面我們介紹過Chromium是基於多進程模型的架構設計,那麼各個進程內的情況呢?事實是每個進程都有很多的線程,特別是browser進程,因而它也基於多線程模型的。介紹多線程機制之前,先來看一下殘酷的現實吧,下面是各個進程的線程信息情況(基於Linux平臺,其它平臺的可能略有不同),相信保證讓你頭大。是的,你需要泡杯茶,然後靜下心來了解一下它們:



爲什麼這麼多的線程呢?Chromium的官方說法告訴我們,主要目的就是爲了保持UI的高響應度,保證UI線程(chrome線程,主線程)不會被任何其它費時的操作阻礙從而影響了對用戶的響應。這些其它的操作很多,例如本地文件讀寫,socket讀寫,數據庫操作等等。既然它們會阻礙其它操作,那好,把它們放在單獨的線程裏自己忙或者等待去吧,所以你就看到那麼多與這些相關的線程(線程名顯然也暴露了這一切)。

問題來了,它們直接如何通信和同步呢?這是多線程的一個非常難纏的問題,因爲這會造成死鎖或者競爭衝突等問題。Chromium精心設計了一套機制來處理它們,那就是絕大多數的場景使用事件和一種chromium新創建的任務傳遞機制,僅在非用不可的情況下使用鎖或者線程安全對象,這有嚴格的要求,詳細的情況請查看以下鏈接以便作近一步的瞭解:http://www.chromium.org/developers/lock-and-condition-variable

問題又來了,那麼每個線程內部是如何處理這些事件和任務的呢?答案是MessageLoop。每個線程會有一個自己的MessageLoop,它們用來處理這些事件和任務。通常的MessageLoop只是處理事件,Chromium中的MessageLoop可以同時處理事件和任務。MessageLoop也是值得研究的,詳細情況我們將在以後的一章加以介紹。

任務和MessageLoop的基本原理如下圖所示。任務被派發到進程的某個線程的MessageLoop的隊列中,MessageLoop會調度執行這些Task。


關於上面這些線程,多數可以通過它們的名字猜出用途,這裏鑑於篇幅和噪音考慮,不一一介紹,下面說明幾個重要和詭異的線程:

chrome線程:進程的主線程,browser進程重要主要是負責UI,當然也是管家;Renderer進程中則是管家兼處理WebKit渲染的;gpu進程中則是負責處理處理繪圖請求並調用openGL進行繪製工作的。

Chrome_IOThread/Chrome_ChildIOThread線程:用來接受來自其它進程的IPC消息和派發自身消息到其它進程。

SignalSender線程: V8 JavaScript引擎中用於處理Linux信號的線程。

##任務(task)

Chromium的特色就是在事件的基礎上,加入了一個新的機制-任務。當需要執行某個操作時候,可以把該操作封裝成一個任務,由任務派發機制傳遞給相應的進程的MessageLoop。

下面看看線程內和線程間分別是如何操作的。

首先看線程內部是如何進行的。但你需要進行費時的操作時候,可以派發一個事件和回調函數給自身線程的MessageLoop,然後MessageLoop會調度該回調函數以執行其操作。問題是這有必要嗎?直接調用不就可以嗎?答案是不可以,或者說是最好不要這麼做,其原因在於,如果當前的MessageLoop裏面有優先級更高的事件和任務需要處理時,你這樣做會阻礙它們的執行。

其次看一看線程間通信。假如一個線程A需要把任務傳遞給一個另外的線程B,大致有三個階段:

1.      首先,線程A把該任務傳遞給線程B;

2.      其次,線程B調度執行該任務;

3.      最後,線程B執行完任務後回覆A。很多情況下,線程A不需要回復。

下圖描述的是一個帶回復的典型的任務傳遞過程。


如果不需要回復,那麼上圖中的‘Reply Task2’就不需要了。當需要回復時候,chromium的做法是新建一個新的任務,該任務封裝原來的任務,新任務被放入線程B,B執行新任務時候調用其Run方法,裏面首先執行原來的任務,然後派發Reply任務給線程A,操作完成。

後面會有一個具體的例子來描述上面這個過程,下面瞭解一下chromium支持Task所涉及的幾個主要類。

Callback: 回調類,其本質是封裝了一個由調用者(例如線程A)設置的回調函數。包含一個run方法,當MessageLoop調度執行該Task時候,運行該方法,該方法調用回調函數

Closure: 定義爲Callback<void(void)>

tracked_objects:一系列的類用於追蹤Task生成位置,編譯調試

Task: 包含一個Closure,追蹤信息,表示一個任務

##一個例子

下面用一個實際的例子來理解線程間是如何傳遞任務的。該例子是chromium中非常常見的一個場景: browser進程接收到來自renderer進程的IPC消息,它由IO線程接收管理,然後派發給chrome線程處理,具體過程如下圖所示:


上面的圖基本上對應了前面的關於任務派發過程的圖,只是少了回覆環節,這是因爲IO 線程並不需要回復。基本的步驟是,當IO線程收到消息後,其派發一個任務,該任務將ChannelProxy::Context::OnDispatchMessage設置會回調函數,這個任務保存在chrome線程的輸入任務隊列中。chrome線程將輸入隊列拷貝到工作隊列後,執行該任務的run方法,該方法會調用回調函數ChannelProxy::Context::OnDispatchMessage,該函數會調用事件的處理函數,這裏也就是RenderProcessHostImpl::OnMessageReceived,這個函數實際上會根據事件類型來調用各個相應函數。

## 源文件目錄

base/threading/

         線程相關的基礎類

## 參考文獻

1.      http://www.chromium.org/developers/design-documents/threading

2.      http://bigasp.com/archives/478

3.      http://www.chromium.org/developers/lock-and-condition-variable

By [email protected]


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章