Chromium 訊息迴圈和執行緒池詳解

2023-10-24 12:00:50

Chromium 中的多執行緒機制由 base 庫提供,要理解 Chromium 中的多執行緒機制,首先要理解的概念就是 base::MessageLoop 和 base::TaskScheduler ,它們兩個是 Chromium 多執行緒的基礎

1. MessageLoop詳解

base::MessageLoop 代表訊息迴圈,它不會主動建立新的執行緒,預設情況下它使用當前執行緒(你也可以手動把它 Bind 到指定的執行緒上),它只負責訊息(任務)迴圈,它提供了 task_runner() 方法用於獲取 TaskRunner 物件,你需要使用 TaskRunner::PostTask*() 方法來向該訊息迴圈分發訊息,預設情況下這些訊息(任務)會在當前執行緒執行。因此,你可以在當前執行緒中建立 MessageLoop 並且在當前執行緒中向它 Post 訊息,並且這些訊息(任務)會在當前執行緒執行。

每一個通過base::Thread建立出來的執行緒都擁有一個 MessageLoop,但是主執行緒(main 函數所在的執行緒)不是通過base::Thread來建立的,它又需要處理訊息迴圈,因此需要手動給主執行緒建立MessageLoop,這個過程一般在程式的入口處進行。

你可以使用 base::MessageLoopCurrent::Get() 靜態方法獲取當前執行緒的MessageLoop物件,從而使用 base::MessageLoopCurrent::Get()->task_runner()→PostTask*() 方法來建立任務。

一旦你建立了一個 MessageLoop 物件,它會自動 Bind 到當前執行緒(通過執行緒依賴的 ThreadLocal 機制來實現)。

 比較常規的使用方式可以參考下面:

 1 #include "base/logging.h"
 2 #include "base/message_loop/message_loop.h"
 3 #include "base/message_loop/message_loop_current.h"
 4 #include "base/task/post_task.h"
 5 #include "base/task/single_thread_task_executor.h"
 6 #include "base/task/thread_pool/thread_pool_impl.h"
 7 #include "base/task/thread_pool/thread_pool_instance.h"
 8 #include "base/threading/thread_task_runner_handle.h"
 9 #include "base/timer/timer.h"
10 
11 void Hello() {
12   LOG(INFO) << "hello,demo!";
13 }
14 
15 int main(int argc, char** argv) {
16   // 建立訊息迴圈
17   base::MessageLoop message_loop;
18   // 也可以使用下面的方法。它們的區別僅在於 MessageLoop 對外暴露了更多的內部介面。
19   // 在當前執行緒建立一個可執行 task 的環境,同樣需要使用 RunLoop 啟動
20   // base::SingleThreadTaskExecutor main_task_executer;
21 
22   base::RunLoop run_loop;
23 
24   // 使用 message_loop 物件直接建立任務
25   message_loop.task_runner()->PostTask(FROM_HERE, base::BindOnce(&Hello));
26   // 獲取當前執行緒的 task runner
27   base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
28                                                 base::BindOnce(&Hello));
29 
30   // 啟動訊息迴圈,即使沒有任務也會阻塞程式執行。當前程序中只有一個執行緒。
31   run_loop.Run();
32 
33   return 0;
34 }

 

2. MessageLoop 的執行流程

 

3. MessageLoop 的類圖

 類圖中已經介紹了主要類的功能,這裡不再贅述,簡單總接一下就是:MessageLoop 建立訊息/任務迴圈,並且繫結到當前執行緒,RunLoop 啟動訊息迴圈,呼叫者通過 TaskRunner 來建立任務。

 

4. TaskScheduler詳解

base::TaskScheduler 直譯為任務排程器,也可以叫做執行緒池,你需要使用它的 static 型別的 Create*() 相關方法來構造它,使用 Start() 方法來啟動它,或者通過 CreateAndStartWithDefaultParams() 方法來同時建立並啟動執行緒池。預設情況下他會建立 3 個執行緒,1 個 Service 執行緒,2 個 Worker 執行緒。Service 執行緒只用來排程延時任務,Worker 執行緒用來執行任務。Service 執行緒繼承自 base::Thread ,因此它內部也包含了 MessageLoop(每一個 base::Thread 類建立出來的執行緒都有一個 MessageLoop)。Worker 執行緒是 TaskScheduler 直接使用 PlatformThread::Create*() 方法建立出來的,因此它不包含 MessageLoop。你可以使用 base::PostTask*() 全域性方法來向執行緒池 Post 任務。

TaskScheduler一般用法如下:

 1 #include <base/logging.h>
 2 #include <base/message_loop/message_loop.h>
 3 #include <base/task/post_task.h>
 4 #include <base/task/task_scheduler/task_scheduler.h>
 5 
 6 void Hello()
 7 {
 8     LOG(INFO)<<"hello,demo!";
 9 }
10 
11 int main(int argc,char** argv)
12 {
13   // 初始化執行緒池,會建立新的執行緒,在新的執行緒中會建立訊息迴圈 MessageLoop
14   base::TaskScheduler::CreateAndStartWithDefaultParams("Demo");
15 
16   // 通過以下方法建立任務
17   base::PostTask(FROM_HERE, base::BindOnce(&Hello));
18   // 或者通過建立新的TaskRunner來建立任務,TaskRunner可以控制任務執行的順序以及是否在同一個執行緒中執行
19   scoped_refptr<base::TaskRunner> task_runner_ =
20     base::CreateTaskRunnerWithTraits({base::TaskPriority::USER_VISIBLE});
21   task_runner_->PostTask(FROM_HERE,base::BindOnce(&Hello));
22 
23   // 不能使用以下方法建立任務,會導致程式崩潰,因為當前執行緒沒有建立訊息迴圈
24   //base::MessageLoopCurrent::Get()->task_runner()->PostTask(FROM_HERE, base::BindOnce(&Hello));
25 
26   // 由於執行緒池預設不會阻塞程式執行,因此這裡為了看到結果使用getchar()阻塞主執行緒。當前程序中共有4個執行緒,1個主執行緒,1個執行緒池Service執行緒,2個Worker執行緒。
27   getchar();
28 
29   return 0;
30 }

 

5. TaskScheduler 的執行流程

 

 6. TaskScheduler 的類圖

 

 7. 總結

  • 每一個 base::Thread 執行緒都擁有一個 MessageLoop 用來進行任務排程;
  • 主執行緒如果需要訊息迴圈,需要自行建立MessageLoop ;
  • 由於 MessageLoop 中維護有 TaskRunner,因此你可以通過獲取該執行緒的 TaskRunner 來給該執行緒 Post 任務;
  • 執行緒池使用更底層的 PlatformThread::Create*() 來直接建立執行緒,從而避免每個執行緒都有 MessageLoop;
  • 執行緒池中的任務是通過 base::PostTask*() 建立的;
  • 執行緒池使用名為 TaskSchedulerSe 的執行緒來排程需要延時的任務,不需要延遲的任務會直接放入執行緒池的任務任務佇列;
  • 通過 TaskRunner 的 PostTask*() 方法會將任務Post到 TaskRunner 所在的 MessageLoop
  • 通過 base::PostTask*() 全域性方法預設會將任務Post到執行緒池中;

 

8. 參考文獻