轉(zhuǎn)載請(qǐng)注明出處:
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是一個(gè)極其多線程的產(chǎn)品。我們努力讓UI盡可能快速響應(yīng),這意味著任何阻塞I/O或者其他昂貴操作不能阻塞UI線程。我們的做法是在線程間傳遞消息作為交流的方式。我們不鼓勵(lì)鎖和線程安全對(duì)象。相反的,對(duì)象僅存在與單個(gè)線程中,我們只為通信而在線程間傳遞消息,我們會(huì)在大多數(shù)跨進(jìn)程請(qǐng)求間使用回調(diào)接口(由消息傳遞實(shí)現(xiàn))。
Thread對(duì)象定義于base/threading/thread.h中。通常你可能會(huì)使用下面描述的已有線程之一而非重新構(gòu)建線程。我們已經(jīng)有了很多難以追蹤的線程。每個(gè)線程有一個(gè)消息循環(huán)(查看base/message_loop/message_loop.h),消息循環(huán)處理這個(gè)線程的消息。你可以使用Thread.message_loop()函數(shù)獲取一個(gè)線程對(duì)應(yīng)的消息循環(huán)。更多關(guān)于消息循環(huán)的內(nèi)容可以在這里查看Anatomy of Chromium MessageLoop.
已有線程
大多數(shù)線程由BrowserProcess對(duì)象管理,它是主“瀏覽器”進(jìn)程的服務(wù)管理器。默認(rèn)情況下,所有的事情都發(fā)生在UI線程中。我們已經(jīng)把某些類的處理過程放到了其他一些線程里。下面這些線程有g(shù)etter接口:
- ui_thread: 應(yīng)用從這個(gè)主線程啟動(dòng)
- io_thread: 某種程度上講,這個(gè)線程起錯(cuò)名字了。它是一個(gè)分發(fā)線程,它處理瀏覽器進(jìn)程和其他所有子進(jìn)程之間的交流。它也是所有資源請(qǐng)求(頁(yè)面加載的)分發(fā)的起點(diǎn)(查看多進(jìn)程架構(gòu))。
- file_thread: 一個(gè)用于文件操作的普通線程。當(dāng)你想要做阻塞文件系統(tǒng)的操作(例如,為某種文件類型請(qǐng)求icon,或者向磁盤寫下載文件),分配給這個(gè)線程。
- db_thread: 用于數(shù)據(jù)庫(kù)操作的線程。例如,cookie服務(wù)在這個(gè)線程上執(zhí)行sqlite操作。注意,歷史記錄數(shù)據(jù)庫(kù)還不會(huì)使用這個(gè)線程。
- safe_browsing_thread
幾個(gè)組件有它們自己的線程:
- History: 歷史記錄服務(wù)有它自己的線程。這可能會(huì)和上面的db_thread合并。然而我們需要保證這會(huì)按照正確的順序發(fā)生 -- 例如,cookie在歷史記錄前會(huì)仙貝加載,因?yàn)槭状渭虞d需要cookie,歷史記錄初始化需要很長(zhǎng)時(shí)間,會(huì)阻塞cookie的加載。
- Proxy service: 查看net/http/http_proxy_service.cc.
- Automation proxy: 這個(gè)線程用于和驅(qū)動(dòng)應(yīng)用的UI測(cè)試程序交流。
保持瀏覽器積極響應(yīng)
正如上面所暗示的,我們?cè)赨I線程里避免任何阻塞I/O,以保持UI積極響應(yīng)。另一個(gè)不太明顯的點(diǎn)是,我們也需要避免io_thread里執(zhí)行阻塞I/O。因?yàn)槿绻覀円虬嘿F的操作阻塞了這個(gè)線程,比如磁盤訪問,那么IPC信息不會(huì)得到處理,結(jié)果就是用戶不能與頁(yè)面進(jìn)行交互。注意異步/平行 IO是可以的。
另一個(gè)需要注意的事情是,不要在一個(gè)線程里阻塞另一個(gè)線程。鎖只能用于交換多線程訪問的共享數(shù)據(jù)。如果一個(gè)線程基于昂貴的計(jì)算或者通過磁盤訪問而更新,那么應(yīng)當(dāng)在不持有鎖的情況下完成緩慢的工作。只有在結(jié)果可用后,鎖才應(yīng)該用于交換新數(shù)據(jù)。一個(gè)例子是,在PluginList::LoadPlugins (src/content/common/plugin_list.cc)中。如果你必須使用鎖,這里有一些最佳實(shí)踐以及一些需要避開的陷阱。
為了編寫不阻塞的代碼,許多Chromium中的API是異步的。通常這意味著他們需要在一個(gè)特殊的線程里執(zhí)行,并通過自定義的裝飾接口返回結(jié)果,或者他們會(huì)在請(qǐng)求操作完成后調(diào)用base::Callback<>對(duì)象。在具體線程執(zhí)行的工作會(huì)在下面的PostTask章節(jié)介紹。
把事情放到其他線程去
base::Callback<>, 異步APIs, 和Currying
base::Callback<> (查看callback.h的文檔) 是有著一個(gè)Run()方法的模板類。它由對(duì)base::Bind的調(diào)用來創(chuàng)建。異步API通常將base::Callback<>作為一種異步返回操作結(jié)果的方式。這是一個(gè)假想的文件閱讀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) 綁定具體的上下文。例如,如果我們有一個(gè)工具函數(shù)DisplayStringWithPrefix,它接受一個(gè)有著前綴的具體參數(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)建適配器,在一個(gè)小的類中將前綴作為成員變量而持有,的替代方案。也要注意“MyPrefix: ”參數(shù)事實(shí)上是一個(gè)const char*,而DisplayStringWithPrefix需要的其實(shí)是const std::string&。正如常見的函數(shù)分配那樣,base::Bind,可能的話,會(huì)進(jìn)行強(qiáng)制參數(shù)類型轉(zhuǎn)化。查看下面的“base::Bind()如何處理參數(shù)”以獲取關(guān)于參數(shù)存儲(chǔ),復(fù)制,以及對(duì)引用的特殊處理的更多細(xì)節(jié)。
PostTask
分發(fā)線程的最底層是使用MessageLoop.PostTask和MessageLoop.PostDelayedTask(查看base/message_loop/message_loop.h)。PostTask會(huì)在一個(gè)特別的線程上進(jìn)行一個(gè)任務(wù)調(diào)度。這個(gè)任務(wù)定義為一個(gè)base::Closure,這是base::Callback<void(void)>的一個(gè)子類型。PostDelayedTask會(huì)在一個(gè)特殊線程里,延遲發(fā)起一個(gè)任務(wù)。這個(gè)任務(wù)由base::Closure類表示,它包含一個(gè)Run()方法,并在base::Bind()被調(diào)用時(shí)創(chuàng)建。處理任務(wù)時(shí),消息循環(huán)最終會(huì)調(diào)用base::CLosure的Run函數(shù),然后丟掉對(duì)任務(wù)對(duì)象的引用。PostTask和PostDelayedTask都會(huì)接收一個(gè)tracked_objects::Location參數(shù),用于輕量級(jí)調(diào)試(掛起的和完成的任務(wù)的計(jì)數(shù)和初始分析可以在調(diào)試構(gòu)建版本中通過about:objects地址進(jìn)行監(jiān)控)。通常FROM_HERE宏的值適合賦給這個(gè)參數(shù)的。
注意新的任務(wù)運(yùn)行在消息循環(huán)隊(duì)列里,任何指定的延遲受操作系統(tǒng)定時(shí)器策略影響。這意味著在Windows平臺(tái),非常小的超時(shí)(10毫秒內(nèi))很可能是不會(huì)發(fā)生的(超時(shí)時(shí)間會(huì)更長(zhǎng))。在PostDelayedTask里將超時(shí)時(shí)間設(shè)置為0也可以用于在當(dāng)前線程里,當(dāng)前進(jìn)程返回消息隊(duì)列之后的某個(gè)時(shí)候。當(dāng)前線程中這樣的一種持續(xù)可以用于確保其他時(shí)間敏感的任務(wù)不會(huì)在這個(gè)線程上進(jìn)入饑餓狀態(tài)。
下面是一個(gè)為一個(gè)功能創(chuàng)建一個(gè)任務(wù)然后在另一個(gè)線程上執(zhí)行這個(gè)任務(wù)的例子(在這個(gè)例子里,在文件線程里):
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)樗鼤?huì)導(dǎo)致一些bug,比如當(dāng)你還持有指針時(shí),它們被刪掉了。更多信息可以在這里找到。
base::Bind()和類方法
base::Bind() API也支持調(diào)用類方法。語(yǔ)法與在一個(gè)函數(shù)里調(diào)用base::Bind()類似,除了第一個(gè)參數(shù)必須是這個(gè)方法所屬的對(duì)象。默認(rèn)情況下,PostTask使用的對(duì)象必須是一個(gè)線程安全引用計(jì)數(shù)對(duì)象。引用計(jì)數(shù)保證了另一個(gè)線程調(diào)用的對(duì)象必須在線程完成前?;?。
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),而且它能保證對(duì)象在任務(wù)正在等待執(zhí)行期間一直保活,你就可以在調(diào)用base::Bind()時(shí)用base::Unratained()包裝這個(gè)對(duì)象指針,以關(guān)閉引用計(jì)數(shù)。這也允許在非引用計(jì)數(shù)的類上使用base::Bind()。但請(qǐng)小心處理這種情況!
base::Bind()怎么處理參數(shù)
傳給base::Bind()的參數(shù)會(huì)被復(fù)制到一個(gè)內(nèi)部InvokerStorage結(jié)構(gòu)對(duì)象(定義在base/bind_internal.h中)。當(dāng)這個(gè)函數(shù)最終執(zhí)行時(shí),它會(huì)查看參數(shù)的這些副本。如果你的目標(biāo)函數(shù)或者方法持有常量引用時(shí),這是很重要的;這些引用會(huì)變成一份參數(shù)的副本。如果你需要原始參數(shù)的引用,你可以用base::ConstRef()包裝參數(shù)。小心使用這個(gè)函數(shù),因?yàn)槿绻玫哪繕?biāo)不能保證在任務(wù)執(zhí)行過程中一直存活的話,這會(huì)很危險(xiǎn)。尤其是,為棧中的變量調(diào)用base::ConstRef()幾乎一定是不安全的,除非你可以保證棧幀不會(huì)在異步任務(wù)完成前無效化。
有時(shí)候,你會(huì)想要傳遞引用技術(shù)對(duì)象作為參數(shù)(請(qǐng)確保使用RefCountedThreadSafe,并且為這些對(duì)象做為基類的純引用計(jì)數(shù))。為了保證對(duì)象在整個(gè)請(qǐng)求期間都能存活,base::Bind()生成的Closure必須持有這個(gè)對(duì)象的引用。這可以通過將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) {
...
}
};
如果你想要不持有引用而傳遞對(duì)象,就要用base::Unretained()包裝參數(shù)。再一次,使用這個(gè)函數(shù)意味著需要有對(duì)對(duì)象的生命周期的外部保證,所以請(qǐng)小心使用!
如果你的對(duì)象有一個(gè)特殊的析構(gòu)函數(shù),它需要在特殊的線程運(yùn)行,你可以使用下面的特性。因?yàn)橛?jì)時(shí)可能導(dǎo)致任務(wù)的代碼展開棧前,任務(wù)就完成了,所以這是必要的:
class MyObject : public base::RefCountedThreadSafe<MyObject, BrowserThread::DeleteOnIOThread> {
撤銷回調(diào)
撤銷任務(wù)主要有兩個(gè)原因(以回調(diào)的形式):
- 你希望在之后對(duì)對(duì)象做一些事情,但在你的回調(diào)運(yùn)行時(shí),你的對(duì)象可能被銷毀。
- 當(dāng)輸入改變時(shí)(例如,用戶輸入),舊的任務(wù)會(huì)變得不必要。出于性能考慮,你應(yīng)該取消它們。
查看下面不同的方式取消任務(wù):
關(guān)于撤銷任務(wù)的重要提示
撤銷一個(gè)持有參數(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)對(duì)象已經(jīng)被銷毀時(shí),Run()調(diào)用會(huì)泄露p。使用scoped_ptr可以修復(fù)這個(gè)bug。
class MyClass {
public:
void DoSomething(scoped_ptr<AnotherClass> p) {
...
}
...
};
base::WeakPtr和撤銷[非線程安全]
你可以使用base::WeakPtr和base::WeakPtrFactory(在base/memory/weak_ptr.h)以確保任何調(diào)用不會(huì)超過它們調(diào)用的對(duì)象的生命周期,而不執(zhí)行引用計(jì)數(shù)。base::Bind機(jī)制對(duì)base::WeakPtr有特殊的理解,會(huì)在base::WeakPtr已經(jīng)失效的情況下終止任務(wù)的執(zhí)行。base::WeakPtrFactory對(duì)象可以用于生成base::WeakPtr實(shí)例,這些實(shí)例被工廠對(duì)象引用。當(dāng)工廠被銷毀時(shí),所有的base::WeakPtr會(huì)設(shè)置它們內(nèi)部的“invalidated”標(biāo)志位,這些標(biāo)志位會(huì)使得與其綁定的任何任務(wù)不被分發(fā)。通過將工廠作為被分發(fā)的對(duì)象的成員,可以實(shí)現(xiàn)自動(dòng)撤銷。
注意:這只在任務(wù)傳遞到相同的線程時(shí)才能生效。當(dāng)前沒有對(duì)于分發(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ù)時(shí)非常有效,它不是線程安全的,因此不能被用于取消運(yùn)行在其他線程的任務(wù)。有時(shí)候會(huì)有關(guān)注性能的需求。例如,我們需要在用戶改變輸入文本時(shí),撤銷在DB線程的數(shù)據(jù)庫(kù)查詢?nèi)蝿?wù)。在這種情況下,CancelableTaskTracker比較合適。
使用CancelableTaskTracker你可以用返回的TaskId撤銷一個(gè)單獨(dú)的任務(wù)。這是使用CancelableTaskTracker而非base::WeakPtr的另一個(gè)原因,即使是在單線程上下文里。
CancelableTaskTracker有兩個(gè)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)用時(shí):
- 如果任務(wù)或者響應(yīng)還沒有開始運(yùn)行,它們都會(huì)被撤銷。
- 如果任務(wù)已經(jīng)在運(yùn)行或者已經(jīng)結(jié)束運(yùn)行,響應(yīng)會(huì)被撤銷。
- 如果響應(yīng)已經(jīng)運(yùn)行或者已經(jīng)結(jié)束運(yùn)行,撤銷就沒有生效。
與base::WeakPtrFactory一樣, CancelableTaskTracker會(huì)在析構(gòu)函數(shù)撤銷所有任務(wù)。
可撤銷的請(qǐng)求(DEPRECATED)
注意,可撤銷的請(qǐng)求已經(jīng)過期了。請(qǐng)不要在新代碼里使用它。為了撤銷運(yùn)行在同一線程中的任務(wù),使用WeakPtr。為了撤銷不同線程中的任務(wù),使用CancelableTaskTracker。
可撤銷的請(qǐng)求使得在另一個(gè)線程上發(fā)起請(qǐng)求,異步返回你想要的數(shù)據(jù)變得容易。和可撤銷存儲(chǔ)系統(tǒng)相同,它使用對(duì)象追蹤原始對(duì)象是否存活。當(dāng)調(diào)用對(duì)象被刪除時(shí),請(qǐng)求會(huì)被撤銷以避免無效的回調(diào)。
和可撤銷存儲(chǔ)系統(tǒng)相同,一個(gè)可撤銷請(qǐng)求的用戶有一個(gè)對(duì)象(在這里,被成為消費(fèi)者),這個(gè)對(duì)象會(huì)追蹤它是否存活,并自動(dòng)撤銷刪除時(shí)任何顯式的請(qǐng)求。
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)者也允許你將請(qǐng)求與具體的數(shù)據(jù)相關(guān)聯(lián)。使用CancelableRequestConsumer可以允許你在調(diào)用請(qǐng)求時(shí),將任意數(shù)據(jù)與provider服務(wù)返回的句柄相關(guān)聯(lián)。當(dāng)請(qǐng)求被撤銷時(shí),數(shù)據(jù)會(huì)自動(dòng)被銷毀。
一個(gè)服務(wù)處理請(qǐng)求繼承自CancelableRequestProvider,這個(gè)對(duì)象提供了方法來撤銷執(zhí)行中的請(qǐng)求,并且會(huì)與消費(fèi)者一同工作以確保所有東西在撤銷時(shí)得到正確的清理。這個(gè)前端服務(wù)只會(huì)追蹤請(qǐng)求,在另一個(gè)線程將它發(fā)送給一個(gè)后端服務(wù)進(jìn)行具體的處理。它大概會(huì)是這樣的:
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);
}
};