環境: flutter sdk v1.5.4-hotfix.1@stable
對應 flutter engine: 52c7a1e849a170be4b2b2fe34142ca2c0a6fea1f
這裡關注的是flutter在C++層的執行緒表示, 沒有涉及dart層的執行緒
執行緒建立
flutter底層(C++)的執行緒(fml::Thread
)是和訊息迴圈緊密關聯的,即每一個fml::Thead
例項都建立了一個訊息迴圈例項,因此如果要建立一個裸執行緒是不應該用fml::Thread
的。fml::Thread
內部即是用C++11的std::thread
來持有一個執行緒物件,參看fml::Thread
建構函式(thread.cc:25)。
執行緒執行體做了2件事
- 建立訊息迴圈例項並關聯執行緒
fml::Thread
物件 - 獲取訊息迴圈的
TaskRunner
物件例項並賦值給執行緒fml::Thread
,即執行緒也持有一個TaskRunner
例項
這個TaskRunner
是個幹啥的,還得看的fml::MessageLoop
實現
fml::Thread
的實現非常簡單,關鍵還是看它關聯的fml::MessageLoop
。
執行緒儲存
訊息迴圈fml::MessageLoop
首先用了執行緒儲存來儲存一個回撥,這個回撥的作用是顯式釋放一個fml::MessageLoop
記憶體物件,所以先搞清flutter底層是如何進行執行緒儲存的。
執行緒儲存物件即作用域與執行緒生命週期一致的儲存物件,fml::ThreadLocal
即為執行緒儲存類,它要儲存的值是一個型別為intptr_t
的物件;
fml::ThreadLocal
在不同平臺用了不同的實現方式
- 類linux平臺
用了pthread的庫函式
pthread_key_create
來生成一個標識執行緒的key鍵,key對應的值是一個輔助類Box
,它儲存了intptr_t
物件和傳入的回撥方法ThreadLocalDestroyCallback
。ThreadLocal
使用前需要宣告的關鍵字是static
物件析構的順序稍有點繞, 各物件析構呼叫序列如下:
ThreadLocal::~ThreadLocal()
ThreadLocal::Box::~Box()
pthread_key_delete(_key)
ThreadLocal::ThreadLocalDestroy
ThreadLocal::Box::DestroyValue
ThreadLocalDestroyCallback() => [](intptr_t value) {}
MessageLoop::~MessageLoop()
ThreadLocal::Box::~Box()
複製程式碼
這樣看似乎thread_local.cc:27
處的delete
操作是多餘的?
- windows平臺
ThreadLocal
使用前直接用了C++11標準的關鍵字thread_local
。
訊息迴圈
訊息迴圈即非同步處理模型,在沒有訊息時阻塞當前執行緒以節省CPU消耗,否則以輪詢的方式空轉很浪費CPU資源,訊息迴圈在安卓平臺上很常見,其實所有的訊息迴圈都大同小異。
關聯執行緒
明白了執行緒儲存,那麼在建立fml::Thread
物件時呼叫的MessageLoop::EnsureInitializedForCurrentThread
就很淺顯了(名字雖然有點累贅),當前執行緒是否建立了訊息迴圈物件,如果沒有那麼建立並儲存。這樣訊息迴圈就與執行緒關聯起來了, 通過什麼關聯的?tls_message_loop
這個執行緒儲存類物件。
訊息佇列
MessageLoopImpl ::delayed_tasks_
就是實際的訊息佇列,它被delayed_tasks_mutex_
這個互斥變數保證執行緒安全。看著有點累贅,其實就是用了一個優先順序佇列按執行時間點來插入,如果時間點相同就按FIFO的規則來插入。
佇列元素是一個內部類DelayedTask
, 主要包含訊息執行體task
和執行時間點target_time
,order
其實是用來排序的。
迴圈實現
MessageLoop
物件建構函式建立了2個重要例項,訊息迴圈實現體MessageLoopImpl
和fml::TaskRunner
, 而fml::TaskRunner
內部又引用了MessageLoopImpl
。MessageLoopImpl::Create()
建立了不同平臺對應的訊息迴圈實現體,於是MessageLoop
與MessageLoopImpl
之間的關係也非常清楚了: MessageLoop
是MessageLoopImpl
的殼或者MessageLoopImpl
是MessageLoop
的代理,MessageLoopImpl
是不對外暴露的、與平臺相關的、真正實現訊息讀取與處理的物件。
MessageLoopImpl::Run,Terminate,WakeUp
是純虛擬函式,由平臺實現,譬如安卓平臺的實現MessageLoopAndroid
呼叫的是AndroidNDK方法ALooper_pollOnce
, MessageLoopLinux
呼叫是Linux阻塞函式epoll_wait
。
這裡涉及的類和方法有點繞,其實想達到目的很簡單:讀取並處理訊息的操作是統一的,但執行緒喚醒或者阻塞的方式是允許平臺差異的
傳送訊息
一個訊息迴圈關聯一個TaskRunner
,而TaskRunner
細看實現發現全都是MessageLoopImpl
的方法,再聯絡之前在AndroidShellHolder
建構函式裡建立的TaskHost
,就可以發現所謂的TaskRunner
無非就是給指定訊息迴圈傳送訊息,而一個訊息迴圈是和一個執行緒(fml::Thread
)關聯的,因而也也就是給指定執行緒傳送訊息,沒錯,正是執行緒間通訊!TaskRunner
也正是宣告成了執行緒安全物件(fml::RefCountedThreadSafe<TaskRunner>
)
這樣其實一切都串聯起來了: fml::TaskRunner
正如android中的android.os.Handler
, fml::closure
正如android中的Runnable
, fml::TaskRunner
不斷的將各種fml::closure
物件新增到訊息佇列當中,並設定訊息迴圈在指定的時間點喚醒並執行。
執行緒結束
fml::Thread
解構函式呼叫了自身的Join
方法, 這個操作初看有點彆扭,後來才明白意圖:主調執行緒需要同步的等待被調執行緒結束,名稱不如Exit
來的言簡意賅。Join
方法先非同步傳送了一個結束訊息迴圈的請求(MessageLoop::GetCurrent().Terminate()
),然後阻塞式等待結束。
結合以上列出執行緒退出的呼叫序列:
Thread::~Thread()
Thread::Join()
TaskRunner::PostTask()
...[非同步]
MessageLoop::Terminate()
MessageLoopImpl::DoTerminate()
MessageLoopImpl::Terminate() => MessageLoopAndroid::Terminate()
ALooper_wake()
...[非同步,函式開始返回]
MessageLoopImpl::Run() => MessageLoopAndroid::Run()
MessageLoopImpl::RunExpiredTasksNow()
MessageLoopImpl::DoRun()
MessageLoop::Run()
...[非同步]
ThreadLocal::~ThreadLocal()
[省略,同執行緒儲存物件析構的呼叫序列]
複製程式碼
執行緒體系
回看AndroidShellHolder
的建構函式,其中涉及flutter::ThreadHost
, fml::TaskRunner
, flutter::TaskRunners
,在建立Shell
物件之前還建立了一系列執行緒:ui_thread
, gpu_thread
, io_thread
,並對TaskRunner
有一系列操作,有點雜亂但現在看其實就非常清晰了。
當前執行AndroidShellHolder
建構函式的執行緒被建立了一個訊息迴圈(android_shell_holder.cc:81)並將訊息迴圈的TaskRunner
賦值給了platform_runner
(注意:並沒有建立platform_thread
物件)。其它的TaskRunner
則分別是所建立的fml::Thread
執行緒的TaskRunner
物件。
那麼問題來了:當某個執行緒通過platform_runner
傳送一個非同步請求時,會在什麼時機執行?