轉(zhuǎn)載請注明出處:
https://ahangchen.gitbooks.io/chromium_doc_zh/content/zh/General_Architecture/Threading.html
全書地址
Chromium中文文檔 for https://www.chromium.org/developers/design-documents
持續(xù)更新ing,歡迎star
gitbook地址:https://ahangchen.gitbooks.io/chromium_doc_zh/content/zh//
github地址: https://github.com/ahangchen/Chromium_doc_zh
概覽
Chromium是一個極其多線程的產(chǎn)品。我們努力讓UI盡可能快速響應(yīng),這意味著任何阻塞I/O或者其他昂貴操作不能阻塞UI線程。我們的做法是在線程間傳遞消息作為交流的方式。我們不鼓勵鎖和線程安全對象。相反的,對象僅存在與單個線程中,我們只為通信而在線程間傳遞消息,我們會在大多數(shù)跨進(jìn)程請求間使用回調(diào)接口(由消息傳遞實(shí)現(xiàn))。
Thread對象定義于base/threading/thread.h中。通常你可能會使用下面描述的已有線程之一而非重新構(gòu)建線程。我們已經(jīng)有了很多難以追蹤的線程。每個線程有一個消息循環(huán)(查看base/message_loop/message_loop.h),消息循環(huán)處理這個線程的消息。你可以使用Thread.message_loop()函數(shù)獲取一個線程對應(yīng)的消息循環(huán)。更多關(guān)于消息循環(huán)的內(nèi)容可以在這里查看Anatomy of Chromium MessageLoop.
已有線程
大多數(shù)線程由BrowserProcess對象管理,它是主“瀏覽器”進(jìn)程的服務(wù)管理器。默認(rèn)情況下,所有的事情都發(fā)生在UI線程中。我們已經(jīng)把某些類的處理過程放到了其他一些線程里。下面這些線程有g(shù)etter接口:
- ui_thread: 應(yīng)用從這個主線程啟動
- io_thread: 某種程度上講,這個線程起錯名字了。它是一個分發(fā)線程,它處理瀏覽器進(jìn)程和其他所有子進(jìn)程之間的交流。它也是所有資源請求(頁面加載的)分發(fā)的起點(diǎn)(查看多進(jìn)程架構(gòu))。
- file_thread: 一個用于文件操作的普通線程。當(dāng)你想要做阻塞文件系統(tǒng)的操作(例如,為某種文件類型請求icon,或者向磁盤寫下載文件),分配給這個線程。
- db_thread: 用于數(shù)據(jù)庫操作的線程。例如,cookie服務(wù)在這個線程上執(zhí)行sqlite操作。注意,歷史記錄數(shù)據(jù)庫還不會使用這個線程。
- safe_browsing_thread
幾個組件有它們自己的線程:
- History: 歷史記錄服務(wù)有它自己的線程。這可能會和上面的db_thread合并。然而我們需要保證這會按照正確的順序發(fā)生 -- 例如,cookie在歷史記錄前會仙貝加載,因?yàn)槭状渭虞d需要cookie,歷史記錄初始化需要很長時間,會阻塞cookie的加載。
- Proxy service: 查看net/http/http_proxy_service.cc.
- Automation proxy: 這個線程用于和驅(qū)動應(yīng)用的UI測試程序交流。
保持瀏覽器積極響應(yīng)
正如上面所暗示的,我們在UI線程里避免任何阻塞I/O,以保持UI積極響應(yīng)。另一個不太明顯的點(diǎn)是,我們也需要避免io_thread里執(zhí)行阻塞I/O。因?yàn)槿绻覀円虬嘿F的操作阻塞了這個線程,比如磁盤訪問,那么IPC信息不會得到處理,結(jié)果就是用戶不能與頁面進(jìn)行交互。注意異步/平行 IO是可以的。
另一個需要注意的事情是,不要在一個線程里阻塞另一個線程。鎖只能用于交換多線程訪問的共享數(shù)據(jù)。如果一個線程基于昂貴的計(jì)算或者通過磁盤訪問而更新,那么應(yīng)當(dāng)在不持有鎖的情況下完成緩慢的工作。只有在結(jié)果可用后,鎖才應(yīng)該用于交換新數(shù)據(jù)。一個例子是,在PluginList::LoadPlugins (src/content/common/plugin_list.cc)中。如果你必須使用鎖,這里有一些最佳實(shí)踐以及一些需要避開的陷阱。
為了編寫不阻塞的代碼,許多Chromium中的API是異步的。通常這意味著他們需要在一個特殊的線程里執(zhí)行,并通過自定義的裝飾接口返回結(jié)果,或者他們會在請求操作完成后調(diào)用base::Callback<>對象。在具體線程執(zhí)行的工作會在下面的PostTask章節(jié)介紹。
把事情放到其他線程去
base::Callback<>, 異步APIs, 和Currying
base::Callback<> (查看callback.h的文檔) 是有著一個Run()方法的模板類。它由對base::Bind的調(diào)用來創(chuàng)建。異步API通常將base::Callback<>作為一種異步返回操作結(jié)果的方式。這是一個假想的文件閱讀API的例子。
void ReadToString(const std::string& filename, const base::Callback<void(const std::string&)>& on_read);
void DisplayString(const std::string& result) {
LOG(INFO) << result;
}
void SomeFunc(const std::string& file) {
ReadToString(file, base::Bind(&DisplayString));
};
在上面的例子中,base::Bind拿到&DisplayString的函數(shù)指針然后將它傳給base::Callback<void(const std::string& result)>。生成的base::Callback<>的類型依賴于傳入?yún)?shù)。為什么不直接傳入函數(shù)指針呢?原因是base::Bind允許調(diào)用者適配功能接口或者通過Currying(http://en.wikipedia.org/wiki/Currying) 綁定具體的上下文。例如,如果我們有一個工具函數(shù)DisplayStringWithPrefix,它接受一個有著前綴的具體參數(shù),我們使用base::Bind以適配接口,如下所示:
void DisplayStringWithPrefix(const std::string& prefix, const std::string& result) {
LOG(INFO) << prefix << result;
}
void AnotherFunc(const std::string& file) {
ReadToString(file, base::Bind(&DisplayStringWithPrefix, "MyPrefix: "));
};
這可以作為創(chuàng)建適配器,在一個小的類中將前綴作為成員變量而持有,的替代方案。也要注意“MyPrefix: ”參數(shù)事實(shí)上是一個const char*,而DisplayStringWithPrefix需要的其實(shí)是const std::string&。正如常見的函數(shù)分配那樣,base::Bind,可能的話,會進(jìn)行強(qiáng)制參數(shù)類型轉(zhuǎn)化。查看下面的“base::Bind()如何處理參數(shù)”以獲取關(guān)于參數(shù)存儲,復(fù)制,以及對引用的特殊處理的更多細(xì)節(jié)。
PostTask
分發(fā)線程的最底層是使用MessageLoop.PostTask和MessageLoop.PostDelayedTask(查看base/message_loop/message_loop.h)。PostTask會在一個特別的線程上進(jìn)行一個任務(wù)調(diào)度。這個任務(wù)定義為一個base::Closure,這是base::Callback<void(void)>的一個子類型。PostDelayedTask會在一個特殊線程里,延遲發(fā)起一個任務(wù)。這個任務(wù)由base::Closure類表示,它包含一個Run()方法,并在base::Bind()被調(diào)用時創(chuàng)建。處理任務(wù)時,消息循環(huán)最終會調(diào)用base::CLosure的Run函數(shù),然后丟掉對任務(wù)對象的引用。PostTask和PostDelayedTask都會接收一個tracked_objects::Location參數(shù),用于輕量級調(diào)試(掛起的和完成的任務(wù)的計(jì)數(shù)和初始分析可以在調(diào)試構(gòu)建版本中通過about:objects地址進(jìn)行監(jiān)控)。通常FROM_HERE宏的值適合賦給這個參數(shù)的。
注意新的任務(wù)運(yùn)行在消息循環(huán)隊(duì)列里,任何指定的延遲受操作系統(tǒng)定時器策略影響。這意味著在Windows平臺,非常小的超時(10毫秒內(nèi))很可能是不會發(fā)生的(超時時間會更長)。在PostDelayedTask里將超時時間設(shè)置為0也可以用于在當(dāng)前線程里,當(dāng)前進(jìn)程返回消息隊(duì)列之后的某個時候。當(dāng)前線程中這樣的一種持續(xù)可以用于確保其他時間敏感的任務(wù)不會在這個線程上進(jìn)入饑餓狀態(tài)。
下面是一個為一個功能創(chuàng)建一個任務(wù)然后在另一個線程上執(zhí)行這個任務(wù)的例子(在這個例子里,在文件線程里):
void WriteToFile(const std::string& filename, const std::string& data);
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
base::Bind(&WriteToFile, "foo.txt", "hello world!"));
你應(yīng)該總使用BrowserThread在線程間提交任務(wù)。永遠(yuǎn)不要緩存MessageLoop指針,因?yàn)樗鼤?dǎo)致一些bug,比如當(dāng)你還持有指針時,它們被刪掉了。更多信息可以在這里找到。
base::Bind()和類方法
base::Bind() API也支持調(diào)用類方法。語法與在一個函數(shù)里調(diào)用base::Bind()類似,除了第一個參數(shù)必須是這個方法所屬的對象。默認(rèn)情況下,PostTask使用的對象必須是一個線程安全引用計(jì)數(shù)對象。引用計(jì)數(shù)保證了另一個線程調(diào)用的對象必須在線程完成前?;?。
class MyObject : public base::RefCountedThreadSafe<MyObject> {
public:
void DoSomething(const std::string16& name) {
thread_->message_loop()->PostTask(
FROM_HERE, base::Bind(&MyObject::DoSomethingOnAnotherThread, this, name));
}
void DoSomethingOnAnotherThread(const std::string16& name) {
...
}
private:
// Always good form to make the destructor private so that only RefCountedThreadSafe can access it.
// This avoids bugs with double deletes.
friend class base::RefCountedThreadSafe<MyObject>;
~MyObject();
Thread* thread_;
};
如果你有外部同步結(jié)構(gòu),而且它能保證對象在任務(wù)正在等待執(zhí)行期間一直?;?,你就可以在調(diào)用base::Bind()時用base::Unratained()包裝這個對象指針,以關(guān)閉引用計(jì)數(shù)。這也允許在非引用計(jì)數(shù)的類上使用base::Bind()。但請小心處理這種情況!
base::Bind()怎么處理參數(shù)
傳給base::Bind()的參數(shù)會被復(fù)制到一個內(nèi)部InvokerStorage結(jié)構(gòu)對象(定義在base/bind_internal.h中)。當(dāng)這個函數(shù)最終執(zhí)行時,它會查看參數(shù)的這些副本。如果你的目標(biāo)函數(shù)或者方法持有常量引用時,這是很重要的;這些引用會變成一份參數(shù)的副本。如果你需要原始參數(shù)的引用,你可以用base::ConstRef()包裝參數(shù)。小心使用這個函數(shù),因?yàn)槿绻玫哪繕?biāo)不能保證在任務(wù)執(zhí)行過程中一直存活的話,這會很危險(xiǎn)。尤其是,為棧中的變量調(diào)用base::ConstRef()幾乎一定是不安全的,除非你可以保證棧幀不會在異步任務(wù)完成前無效化。
有時候,你會想要傳遞引用技術(shù)對象作為參數(shù)(請確保使用RefCountedThreadSafe,并且為這些對象做為基類的純引用計(jì)數(shù))。為了保證對象在整個請求期間都能存活,base::Bind()生成的Closure必須持有這個對象的引用。這可以通過將scoped_refptr作為參數(shù)類型傳遞,或者用make_scoped_refptr()包裝裸指針來完成:
class SomeParamObject : public base::RefCountedThreadSafe<SomeParamObject> {
...
};
class MyObject : public base::RefCountedThreadSafe<MyObject> {
public:
void DoSomething() {
scoped_refptr<SomeParamObject> param(new SomeParamObject);
thread_->message_loop()->PostTask(FROM_HERE
base::Bind(&MyObject::DoSomethingOnAnotherThread, this, param));
}
void DoSomething2() {
SomeParamObject* param = new SomeParamObject;
thread_->message_loop()->PostTask(FROM_HERE
base::Bind(&MyObject::DoSomethingOnAnotherThread, this,
make_scoped_refptr(param)));
}
// Note how this takes a raw pointer. The important part is that
// base::Bind() was passed a scoped_refptr; using a scoped_refptr
// here would result in an extra AddRef()/Release() pair.
void DoSomethingOnAnotherThread(SomeParamObject* param) {
...
}
};
如果你想要不持有引用而傳遞對象,就要用base::Unretained()包裝參數(shù)。再一次,使用這個函數(shù)意味著需要有對對象的生命周期的外部保證,所以請小心使用!
如果你的對象有一個特殊的析構(gòu)函數(shù),它需要在特殊的線程運(yùn)行,你可以使用下面的特性。因?yàn)橛?jì)時可能導(dǎo)致任務(wù)的代碼展開棧前,任務(wù)就完成了,所以這是必要的:
class MyObject : public base::RefCountedThreadSafe<MyObject, BrowserThread::DeleteOnIOThread> {
撤銷回調(diào)
撤銷任務(wù)主要有兩個原因(以回調(diào)的形式):
- 你希望在之后對對象做一些事情,但在你的回調(diào)運(yùn)行時,你的對象可能被銷毀。
- 當(dāng)輸入改變時(例如,用戶輸入),舊的任務(wù)會變得不必要。出于性能考慮,你應(yīng)該取消它們。
查看下面不同的方式取消任務(wù):
關(guān)于撤銷任務(wù)的重要提示
撤銷一個持有參數(shù)的任務(wù)是很危險(xiǎn)的。查看下面的例子(這里例子使用base::WeakPtr以執(zhí)行撤銷操作,但問題適用于其他情景)。
class MyClass {
public:
// Owns |p|.
void DoSomething(AnotherClass* p) {
...
}
WeakPtr<MyClass> AsWeakPtr() {
return weak_factory_.GetWeakPtr();
}
private:
base::WeakPtrFactory<MyClass> weak_factory_;
};
...
Closure cancelable_closure = Bind(&MyClass::DoSomething, object->AsWeakPtr(), p);
Callback<void(AnotherClass*)> cancelable_callback = Bind(&MyClass::DoSomething, object->AsWeakPtr());
...
void FunctionRunLater(const Closure& cancelable_closure,
const Callback<void(AnotherClass*)>& cancelable_callback) {
// Leak memory!
cancelable_closure.Run();
cancelable_callback.Run(p);
}
在FunctionRunLater中,當(dāng)對象已經(jīng)被銷毀時,Run()調(diào)用會泄露p。使用scoped_ptr可以修復(fù)這個bug。
class MyClass {
public:
void DoSomething(scoped_ptr<AnotherClass> p) {
...
}
...
};
base::WeakPtr和撤銷[非線程安全]
你可以使用base::WeakPtr和base::WeakPtrFactory(在base/memory/weak_ptr.h)以確保任何調(diào)用不會超過它們調(diào)用的對象的生命周期,而不執(zhí)行引用計(jì)數(shù)。base::Bind機(jī)制對base::WeakPtr有特殊的理解,會在base::WeakPtr已經(jīng)失效的情況下終止任務(wù)的執(zhí)行。base::WeakPtrFactory對象可以用于生成base::WeakPtr實(shí)例,這些實(shí)例被工廠對象引用。當(dāng)工廠被銷毀時,所有的base::WeakPtr會設(shè)置它們內(nèi)部的“invalidated”標(biāo)志位,這些標(biāo)志位會使得與其綁定的任何任務(wù)不被分發(fā)。通過將工廠作為被分發(fā)的對象的成員,可以實(shí)現(xiàn)自動撤銷。
注意:這只在任務(wù)傳遞到相同的線程時才能生效。當(dāng)前沒有對于分發(fā)到其他線程的任務(wù)能夠生效的普適解決方案。查看下一節(jié),關(guān)于CancelableTaskTracker,了解其他解決方案。
class MyObject {
public:
MyObject() : weak_factory_(this) {}
void DoSomething() {
const int kDelayMS = 100;
MessageLoop::current()->PostDelayedTask(FROM_HERE,
base::Bind(&MyObject::DoSomethingLater, weak_factory_.GetWeakPtr()),
kDelayMS);
}
void DoSomethingLater() {
...
}
private:
base::WeakPtrFactory<MyObject> weak_factory_;
};
CancelableTaskTracker
當(dāng)base::WeakPtr在撤銷任務(wù)時非常有效,它不是線程安全的,因此不能被用于取消運(yùn)行在其他線程的任務(wù)。有時候會有關(guān)注性能的需求。例如,我們需要在用戶改變輸入文本時,撤銷在DB線程的數(shù)據(jù)庫查詢?nèi)蝿?wù)。在這種情況下,CancelableTaskTracker比較合適。
使用CancelableTaskTracker你可以用返回的TaskId撤銷一個單獨(dú)的任務(wù)。這是使用CancelableTaskTracker而非base::WeakPtr的另一個原因,即使是在單線程上下文里。
CancelableTaskTracker有兩個Post方法,它們做的事情和base::TaskRunner里的post方法一樣,但有額外的撤銷支持。
class UserInputHandler : public base::RefCountedThreadSafe<UserInputHandler> {
// Runs on UI thread.
void OnUserInput(Input input) {
CancelPreviousTask();
DBResult* result = new DBResult();
task_id_ = tracker_->PostTaskAndReply(
BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB).get(),
FROM_HERE,
base::Bind(&LookupHistoryOnDBThread, this, input, result),
base::Bind(&ShowHistoryOnUIThread, this, base::Owned(result)));
}
void CancelPreviousTask() {
tracker_->TryCancel(task_id_);
}
...
private:
CancelableTaskTracker tracker_; // Cancels all pending tasks while destruction.
CancelableTaskTracker::TaskId task_id_;
...
};
因?yàn)槿蝿?wù)運(yùn)行在其他線程上,沒有保證它可以被成功撤銷。
當(dāng)TryCancel()被調(diào)用時:
- 如果任務(wù)或者響應(yīng)還沒有開始運(yùn)行,它們都會被撤銷。
- 如果任務(wù)已經(jīng)在運(yùn)行或者已經(jīng)結(jié)束運(yùn)行,響應(yīng)會被撤銷。
- 如果響應(yīng)已經(jīng)運(yùn)行或者已經(jīng)結(jié)束運(yùn)行,撤銷就沒有生效。
與base::WeakPtrFactory一樣, CancelableTaskTracker會在析構(gòu)函數(shù)撤銷所有任務(wù)。
可撤銷的請求(DEPRECATED)
注意,可撤銷的請求已經(jīng)過期了。請不要在新代碼里使用它。為了撤銷運(yùn)行在同一線程中的任務(wù),使用WeakPtr。為了撤銷不同線程中的任務(wù),使用CancelableTaskTracker。
可撤銷的請求使得在另一個線程上發(fā)起請求,異步返回你想要的數(shù)據(jù)變得容易。和可撤銷存儲系統(tǒng)相同,它使用對象追蹤原始對象是否存活。當(dāng)調(diào)用對象被刪除時,請求會被撤銷以避免無效的回調(diào)。
和可撤銷存儲系統(tǒng)相同,一個可撤銷請求的用戶有一個對象(在這里,被成為消費(fèi)者),這個對象會追蹤它是否存活,并自動撤銷刪除時任何顯式的請求。
class MyClass {
void MakeRequest() {
frontend_service->StartRequest(some_input1, some_input2, this,
// Use base::Unretained(this) if this may cause a refcount cycle.
base::Bind(&MyClass:RequestComplete, this));
}
void RequestComplete(int status) {
...
}
private:
CancelableRequestConsumer consumer_;
};
注意這里MyClass::RequestComplete與base::Unretained(this)綁定。
消費(fèi)者也允許你將請求與具體的數(shù)據(jù)相關(guān)聯(lián)。使用CancelableRequestConsumer可以允許你在調(diào)用請求時,將任意數(shù)據(jù)與provider服務(wù)返回的句柄相關(guān)聯(lián)。當(dāng)請求被撤銷時,數(shù)據(jù)會自動被銷毀。
一個服務(wù)處理請求繼承自CancelableRequestProvider,這個對象提供了方法來撤銷執(zhí)行中的請求,并且會與消費(fèi)者一同工作以確保所有東西在撤銷時得到正確的清理。這個前端服務(wù)只會追蹤請求,在另一個線程將它發(fā)送給一個后端服務(wù)進(jìn)行具體的處理。它大概會是這樣的:
class FrontendService : public CancelableRequestProvider {
typedef base::Callback<void(int)> RequestCallbackType;
Handle StartRequest(int some_input1, int some_input2,
CallbackConsumer* consumer,
const RequestCallbackType& callback) {
scoped_refptr< CancelableRequest<FrontendService::RequestCallbackType> >
request(new CancelableRequest(callback));
AddRequest(request, consumer);
// Send the parameters and the request to the backend thread.
backend_thread_->PostTask(FROM_HERE,
base::Bind(&BackendService::DoRequest, backend_, request,
some_input1, some_input2), 0);
// The handle will have been set by AddRequest.
return request->handle();
}
};
后端服務(wù)允許在其他線程上,它執(zhí)行處理過程,并將結(jié)果轉(zhuǎn)發(fā)回原始調(diào)用者。它大概是這樣的:
class BackendService : public base::RefCountedThreadSafe<BackendService> {
void DoRequest(
scoped_refptr< CancelableRequest<FrontendService::RequestCallbackType> >
request,
int some_input1, int some_input2) {
if (request->canceled())
return;
... do your processing ...
// Execute ForwardResult() like you would do Run() on the base::Callback<>.
request->ForwardResult(return_value);
}
};