【Chromium中文文檔】線程

線程

轉載請註明出處:https://ahangchen.gitbooks.io/chromium_doc_zh/content/zh//General_Architecture/Threading.html

全書地址
Chromium中文文檔 for https://www.chromium.org/developers/design-documents
持續更新ing,歡迎star
gitbook地址:https://ahangchen.gitbooks.io/chromium_doc_zh/content/zh//
github地址: https://github.com/ahangchen/Chromium_doc_zh

概覽

Chromium是一個極其多線程的產品。我們努力讓UI儘可能快速響應,這意味着任何阻塞I/O或者其他昂貴操作不能阻塞UI線程。我們的做法是在線程間傳遞消息作爲交流的方式。我們不鼓勵鎖和線程安全對象。相反的,對象僅存在與單個線程中,我們只爲通信而在線程間傳遞消息,我們會在大多數跨進程請求間使用回調接口(由消息傳遞實現)。

Thread對象定義於base/threading/thread.h中。通常你可能會使用下面描述的已有線程之一而非重新構建線程。我們已經有了很多難以追蹤的線程。每個線程有一個消息循環(查看base/message_loop/message_loop.h),消息循環處理這個線程的消息。你可以使用Thread.message_loop()函數獲取一個線程對應的消息循環。更多關於消息循環的內容可以在這裏查看Anatomy of Chromium MessageLoop.

已有線程

大多數線程由BrowserProcess對象管理,它是主“瀏覽器”進程的服務管理器。默認情況下,所有的事情都發生在UI線程中。我們已經把某些類的處理過程放到了其他一些線程裏。下面這些線程有getter接口:

  • ui_thread: 應用從這個主線程啓動
  • io_thread: 某種程度上講,這個線程起錯名字了。它是一個分發線程,它處理瀏覽器進程和其他所有子進程之間的交流。它也是所有資源請求(頁面加載的)分發的起點(查看多進程架構)。
  • file_thread: 一個用於文件操作的普通線程。當你想要做阻塞文件系統的操作(例如,爲某種文件類型請求icon,或者向磁盤寫下載文件),分配給這個線程。
  • db_thread: 用於數據庫操作的線程。例如,cookie服務在這個線程上執行sqlite操作。注意,歷史記錄數據庫還不會使用這個線程。
  • safe_browsing_thread

幾個組件有它們自己的線程:

  • History: 歷史記錄服務有它自己的線程。這可能會和上面的db_thread合併。然而我們需要保證這會按照正確的順序發生 – 例如,cookie在歷史記錄前會仙貝加載,因爲首次加載需要cookie,歷史記錄初始化需要很長時間,會阻塞cookie的加載。
  • Proxy service: 查看net/http/http_proxy_service.cc.
  • Automation proxy: 這個線程用於和驅動應用的UI測試程序交流。

保持瀏覽器積極響應

正如上面所暗示的,我們在UI線程裏避免任何阻塞I/O,以保持UI積極響應。另一個不太明顯的點是,我們也需要避免io_thread裏執行阻塞I/O。因爲如果我們因昂貴的操作阻塞了這個線程,比如磁盤訪問,那麼IPC信息不會得到處理,結果就是用戶不能與頁面進行交互。注意異步/平行 IO是可以的。

另一個需要注意的事情是,不要在一個線程裏阻塞另一個線程。鎖只能用於交換多線程訪問的共享數據。如果一個線程基於昂貴的計算或者通過磁盤訪問而更新,那麼應當在不持有鎖的情況下完成緩慢的工作。只有在結果可用後,鎖才應該用於交換新數據。一個例子是,在PluginList::LoadPlugins (src/content/common/plugin_list.cc)中。如果你必須使用鎖,這裏有一些最佳實踐以及一些需要避開的陷阱。

爲了編寫不阻塞的代碼,許多Chromium中的API是異步的。通常這意味着他們需要在一個特殊的線程裏執行,並通過自定義的裝飾接口返回結果,或者他們會在請求操作完成後調用base::Callback<>對象。在具體線程執行的工作會在下面的PostTask章節介紹。

把事情放到其他線程去

base::Callback<>, 異步APIs, 和Currying

base::Callback<> (查看callback.h的文檔) 是有着一個Run()方法的模板類。它由對base::Bind的調用來創建。異步API通常將base::Callback<>作爲一種異步返回操作結果的方式。這是一個假想的文件閱讀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的函數指針然後將它傳給base::Callback<void(const std::string& result)>。生成的base::Callback<>的類型依賴於傳入參數。爲什麼不直接傳入函數指針呢?原因是base::Bind允許調用者適配功能接口或者通過Currying(http://en.wikipedia.org/wiki/Currying) 綁定具體的上下文。例如,如果我們有一個工具函數DisplayStringWithPrefix,它接受一個有着前綴的具體參數,我們使用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: "));
};

這可以作爲創建適配器,在一個小的類中將前綴作爲成員變量而持有,的替代方案。也要注意“MyPrefix: ”參數事實上是一個const char*,而DisplayStringWithPrefix需要的其實是const std::string&。正如常見的函數分配那樣,base::Bind,可能的話,會進行強制參數類型轉化。查看下面的“base::Bind()如何處理參數”以獲取關於參數存儲,複製,以及對引用的特殊處理的更多細節。

PostTask

分發線程的最底層是使用MessageLoop.PostTask和MessageLoop.PostDelayedTask(查看base/message_loop/message_loop.h)。PostTask會在一個特別的線程上進行一個任務調度。這個任務定義爲一個base::Closure,這是base::Callback<void(void)>的一個子類型。PostDelayedTask會在一個特殊線程裏,延遲發起一個任務。這個任務由base::Closure類表示,它包含一個Run()方法,並在base::Bind()被調用時創建。處理任務時,消息循環最終會調用base::CLosure的Run函數,然後丟掉對任務對象的引用。PostTask和PostDelayedTask都會接收一個tracked_objects::Location參數,用於輕量級調試(掛起的和完成的任務的計數和初始分析可以在調試構建版本中通過about:objects地址進行監控)。通常FROM_HERE宏的值適合賦給這個參數的。

注意新的任務運行在消息循環隊列裏,任何指定的延遲受操作系統定時器策略影響。這意味着在Windows平臺,非常小的超時(10毫秒內)很可能是不會發生的(超時時間會更長)。在PostDelayedTask裏將超時時間設置爲0也可以用於在當前線程裏,當前進程返回消息隊列之後的某個時候。當前線程中這樣的一種持續可以用於確保其他時間敏感的任務不會在這個線程上進入飢餓狀態。

下面是一個爲一個功能創建一個任務然後在另一個線程上執行這個任務的例子(在這個例子裏,在文件線程裏):

void WriteToFile(const std::string& filename, const std::string& data);
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
                        base::Bind(&WriteToFile, "foo.txt", "hello world!"));

你應該總使用BrowserThread在線程間提交任務。永遠不要緩存MessageLoop指針,因爲它會導致一些bug,比如當你還持有指針時,它們被刪掉了。更多信息可以在這裏找到。

base::Bind()和類方法

base::Bind() API也支持調用類方法。語法與在一個函數裏調用base::Bind()類似,除了第一個參數必須是這個方法所屬的對象。默認情況下,PostTask使用的對象必須是一個線程安全引用計數對象。引用計數保證了另一個線程調用的對象必須在線程完成前保活。

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_;
};

如果你有外部同步結構,而且它能保證對象在任務正在等待執行期間一直保活,你就可以在調用base::Bind()時用base::Unratained()包裝這個對象指針,以關閉引用計數。這也允許在非引用計數的類上使用base::Bind()。但請小心處理這種情況!

base::Bind()怎麼處理參數

傳給base::Bind()的參數會被複制到一個內部InvokerStorage結構對象(定義在base/bind_internal.h中)。當這個函數最終執行時,它會查看參數的這些副本。如果你的目標函數或者方法持有常量引用時,這是很重要的;這些引用會變成一份參數的副本。如果你需要原始參數的引用,你可以用base::ConstRef()包裝參數。小心使用這個函數,因爲如果引用的目標不能保證在任務執行過程中一直存活的話,這會很危險。尤其是,爲棧中的變量調用base::ConstRef()幾乎一定是不安全的,除非你可以保證棧幀不會在異步任務完成前無效化。

有時候,你會想要傳遞引用技術對象作爲參數(請確保使用RefCountedThreadSafe,並且爲這些對象做爲基類的純引用計數)。爲了保證對象在整個請求期間都能存活,base::Bind()生成的Closure必須持有這個對象的引用。這可以通過將scoped_refptr作爲參數類型傳遞,或者用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()包裝參數。再一次,使用這個函數意味着需要有對對象的生命週期的外部保證,所以請小心使用!

如果你的對象有一個特殊的析構函數,它需要在特殊的線程運行,你可以使用下面的特性。因爲計時可能導致任務的代碼展開棧前,任務就完成了,所以這是必要的:

class MyObject : public base::RefCountedThreadSafe<MyObject, BrowserThread::DeleteOnIOThread> {

撤銷回調

撤銷任務主要有兩個原因(以回調的形式):

  • 你希望在之後對對象做一些事情,但在你的回調運行時,你的對象可能被銷燬。
  • 當輸入改變時(例如,用戶輸入),舊的任務會變得不必要。出於性能考慮,你應該取消它們。

查看下面不同的方式取消任務:

關於撤銷任務的重要提示

撤銷一個持有參數的任務是很危險的。查看下面的例子(這裏例子使用base::WeakPtr以執行撤銷操作,但問題適用於其他情景)。

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中,當對象已經被銷燬時,Run()調用會泄露p。使用scoped_ptr可以修復這個bug。

class MyClass {
 public:
  void DoSomething(scoped_ptr<AnotherClass> p) {
    ...
  }
  ...
};

base::WeakPtr和撤銷[非線程安全]

你可以使用base::WeakPtr和base::WeakPtrFactory(在base/memory/weak_ptr.h)以確保任何調用不會超過它們調用的對象的生命週期,而不執行引用計數。base::Bind機制對base::WeakPtr有特殊的理解,會在base::WeakPtr已經失效的情況下終止任務的執行。base::WeakPtrFactory對象可以用於生成base::WeakPtr實例,這些實例被工廠對象引用。當工廠被銷燬時,所有的base::WeakPtr會設置它們內部的“invalidated”標誌位,這些標誌位會使得與其綁定的任何任務不被分發。通過將工廠作爲被分發的對象的成員,可以實現自動撤銷。

注意:這隻在任務傳遞到相同的線程時才能生效。當前沒有對於分發到其他線程的任務能夠生效的普適解決方案。查看下一節,關於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

當base::WeakPtr在撤銷任務時非常有效,它不是線程安全的,因此不能被用於取消運行在其他線程的任務。有時候會有關注性能的需求。例如,我們需要在用戶改變輸入文本時,撤銷在DB線程的數據庫查詢任務。在這種情況下,CancelableTaskTracker比較合適。

使用CancelableTaskTracker你可以用返回的TaskId撤銷一個單獨的任務。這是使用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_;
  ...
};

因爲任務運行在其他線程上,沒有保證它可以被成功撤銷。

當TryCancel()被調用時:

  • 如果任務或者響應還沒有開始運行,它們都會被撤銷。
  • 如果任務已經在運行或者已經結束運行,響應會被撤銷。
  • 如果響應已經運行或者已經結束運行,撤銷就沒有生效。

與base::WeakPtrFactory一樣, CancelableTaskTracker會在析構函數撤銷所有任務。

可撤銷的請求(DEPRECATED)

注意,可撤銷的請求已經過期了。請不要在新代碼裏使用它。爲了撤銷運行在同一線程中的任務,使用WeakPtr。爲了撤銷不同線程中的任務,使用CancelableTaskTracker。

可撤銷的請求使得在另一個線程上發起請求,異步返回你想要的數據變得容易。和可撤銷存儲系統相同,它使用對象追蹤原始對象是否存活。當調用對象被刪除時,請求會被撤銷以避免無效的回調。

和可撤銷存儲系統相同,一個可撤銷請求的用戶有一個對象(在這裏,被成爲消費者),這個對象會追蹤它是否存活,並自動撤銷刪除時任何顯式的請求。

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)綁定。

消費者也允許你將請求與具體的數據相關聯。使用CancelableRequestConsumer可以允許你在調用請求時,將任意數據與provider服務返回的句柄相關聯。當請求被撤銷時,數據會自動被銷燬。

一個服務處理請求繼承自CancelableRequestProvider,這個對象提供了方法來撤銷執行中的請求,並且會與消費者一同工作以確保所有東西在撤銷時得到正確的清理。這個前端服務只會追蹤請求,在另一個線程將它發送給一個後端服務進行具體的處理。它大概會是這樣的:

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();
  }
};

後端服務允許在其他線程上,它執行處理過程,並將結果轉發回原始調用者。它大概是這樣的:

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);
  }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章