flutter: 線程通信與消息循環(C++)


環境: 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件事

  1. 創建消息循環實例並關聯線程fml::Thread對象
  2. 獲取消息循環的TaskRunner對象實例並賦值給線程fml::Thread,即線程也持有一個TaskRunner實例

這個TaskRunner是個干啥的,還得看的fml::MessageLoop實現
fml::Thread的實現非常簡單,關鍵還是看它關聯的fml::MessageLoop

線程存儲

消息循環fml::MessageLoop首先用了線程存儲來保存一個回調,這個回調的作用是顯式釋放一個fml::MessageLoop內存對象,所以先搞清flutter底層是如何進行線程存儲的。

線程存儲對象即作用域與線程生命周期一致的存儲對象,fml::ThreadLocal即為線程存儲類,它要保存的值是一個類型為intptr_t的對象;
fml::ThreadLocal在不同平台用了不同的實現方式

  1. 類linux平台
    用了pthread的庫函數pthread_key_create來生成一個標識線程的key鍵,key對應的值是一個輔助類Box,它保存了intptr_t對象和傳入的回調方法ThreadLocalDestroyCallbackThreadLocal使用前需要聲明的關鍵字是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操作是多余的?
2. 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_timeorder其實是用來排序的。

循環實現

MessageLoop對象構造函數創建了2個重要實例,消息循環實現體MessageLoopImplfml::TaskRunner, 而fml::TaskRunner內部又引用了MessageLoopImplMessageLoopImpl::Create()創建了不同平台對應的消息循環實現體,於是MessageLoopMessageLoopImpl之間的關系也非常清楚了: MessageLoopMessageLoopImpl的殼或者MessageLoopImplMessageLoop的代理,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發送一個異步請求時,會在什么時機執行?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM