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. 參考文獻