FlutterEngine執行緒管理與DartIsolate機制
閒魚技術-福居
本文結合Flutter Engine官方文件討論了Flutter Engine內的執行緒管理模式以及Dart Isolate機制。
Flutter 是什麼?
Flutter簡介
Flutter是Google主導開發的高質量高效能移動跨平臺UI開發套件。使用Flutter你可以使用Dart語言高效快速開發高質量的跨平臺App,同時Flutter還可以可以與現存的Native程式碼相容。目前在世界範圍內被眾多開發者和組織使用,而且它是開源免費的!
Flutter優勢與前景
目前Flutter拿來比較最多的是Reactive Native,實際上Flutter跟RN有本質的區別。Flutter UI渲染是自己實現,這跟RN JS Bridge的形式有區別。這也是Flutter效能的一個突破點。使用Flutter用開發效率高,執行效率高,UI靈活性擴充套件性高等特點。相對於JS Bridge擴充套件型的跨平臺實現,Flutter有著更加廣闊的想象空間。
更加詳細的介紹可以瀏覽此連結:Flutter IO
Flutter 執行緒管理簡述
Flutter Engine自己不建立管理執行緒。Flutter Engine執行緒的建立和管理是由embedder負責的。
注意:Embeder是指將引擎移植的平臺的中間層程式碼。
Flutter Engine要求Embeder提供四個Task Runner。儘管Flutter Engine不在乎Runner具體跑在哪個執行緒,但是它需要執行緒配置在整一個生命週期裡面保持穩定。也就是說一個Runner最好始終保持在同一執行緒執行。這四個主要的Task Runner包括:
Platform Task Runner
Flutter Engine的主Task Runner,執行Platform Task Runner的執行緒可以理解為是主執行緒。類似於Android Main Thread或者iOS的Main Thread。但是我們要注意Platform Task Runner和iOS之類的主執行緒還是有區別的。
對於Flutter Engine來說Platform Runner所在的執行緒跟其它執行緒並沒有實質上的區別,只不過我們人為賦予它特定的含義便於理解區分。實際上我們可以同時啟動多個Engine例項,每個Engine對應一個Platform Runner,每個Runner跑在各自的執行緒裡。這也是Fuchsia(Google正在開發的操作引擎)裡Content Handler的工作原理。一般來說,一個Flutter應用啟動的時候會建立一個Engine例項,Engine建立的時候會建立一個執行緒供Platform Runner使用。
跟Flutter Engine的所有互動(介面呼叫)必須發生在Platform Thread,試圖在其它執行緒中呼叫Flutter Engine會導致無法預期的異常。這跟iOS UI相關的操作都必須在主執行緒進行相類似。需要注意的是在Flutter Engine中有很多模組都是非執行緒安全的。一旦引擎正常啟動執行起來,所有引擎API呼叫都將在Platform Thread裡發生。
Platform Runner所在的Thread不僅僅處理與Engine互動,它還處理來自平臺的訊息。這樣的處理比較方便的,因為幾乎所有引擎的呼叫都只有在Platform Thread進行才能是安全的,Native Plugins不必要做額外的執行緒操作就可以保證操作能夠在Platform Thread進行。如果Plugin自己啟動了額外的執行緒,那麼它需要負責將返回結果派發回Platform Thread以便Dart能夠安全地處理。規則很簡單,對於Flutter Engine的介面呼叫都需保證在Platform Thread進行。
需要注意的是,阻塞Platform Thread不會直接導致Flutter應用的卡頓(跟iOS android主執行緒不同)。儘管如此,平臺對Platform Thread還是有強制執行限制。所以建議複雜計算邏輯操作不要放在Platform Thread而是放在其它執行緒(不包括我們現在討論的這個四個執行緒)。其他執行緒處理完畢後將結果轉發回Platform Thread。長時間卡住Platform Thread應用有可能會被系統Watchdot強行殺死。
UI Task Runner Thread(Dart Runner)
UI Task Runner被Flutter Engine用於執行Dart root isolate程式碼(isolate我們後面會講到,姑且先簡單理解為Dart VM裡面的執行緒)。Root isolate比較特殊,它繫結了不少Flutter需要的函式方法。Root isolate執行應用的main code。引擎啟動的時候為其增加了必要的繫結,使其具備排程提交渲染幀的能力。對於每一幀,引擎要做的事情有:
- Root isolate通知Flutter Engine有幀需要渲染。
- Flutter Engine通知平臺,需要在下一個vsync的時候得到通知。
- 平臺等待下一個vsync
- 對建立的物件和Widgets進行Layout並生成一個Layer Tree,這個Tree馬上被提交給Flutter Engine。當前階段沒有進行任何光柵化,這個步驟僅是生成了對需要繪製內容的描述。
- 建立或者更新Tree,這個Tree包含了用於螢幕上顯示Widgets的語義資訊。這個東西主要用於平臺相關的輔助Accessibility元素的配置和渲染。
除了渲染相關邏輯之外Root Isolate還是處理來自Native Plugins的訊息響應,Timers,Microtasks和非同步IO。
我們看到Root Isolate負責建立管理的Layer Tree最終決定什麼內容要繪製到螢幕上。因此這個執行緒的過載會直接導致卡頓掉幀。
如果確實有無法避免的繁重計算,建議將其放到獨立的Isolate去執行,比如使用compute關鍵字或者放到非Root Isolate,這樣可以避免應用UI卡頓。但是需要注意的是非Root Isolate缺少Flutter引擎需要的一些函式繫結,你無法在這個Isolate直接與Flutter Engine互動。所以只在需要大量計算的時候採用獨立Isolate。
GPU Task Runner
GPU Task Runner被用於執行裝置GPU的相關呼叫。UI Task Runner建立的Layer Tree資訊是平臺不相關,也就是說Layer Tree提供了繪製所需要的資訊,具體如何實現繪製取決於具體平臺和方式,可以是OpenGL,Vulkan,軟體繪製或者其他Skia配置的繪圖實現。GPU Task Runner中的模組負責將Layer Tree提供的資訊轉化為實際的GPU指令。GPU Task Runner同時也負責配置管理每一幀繪製所需要的GPU資源,這包括平臺Framebuffer的建立,Surface生命週期管理,保證Texture和Buffers在繪製的時候是可用的。
基於Layer Tree的處理時長和GPU幀顯示到螢幕的耗時,GPU Task Runner可能會延遲下一幀在UI Task Runner的排程。一般來說UI Runner和GPU Runner跑在不同的執行緒。存在這種可能,UI Runner在已經準備好了下一幀的情況下,GPU Runner卻還正在向GPU提交上一幀。這種延遲排程機制確保不讓UI Runner分配過多的任務給GPU Runner。
前面我們提到GPU Runner可以導致UI Runner的幀排程的延遲,GPU Runner的過載會導致Flutter應用的卡頓。一般來說使用者沒有機會向GPU Runner直接提交任務,因為平臺和Dart程式碼都無法跑進GPU Runner。但是Embeder還是可以向GPU Runner提交任務的。因此建議為每一個Engine例項都新建一個專用的GPU Runner執行緒。
IO Task Runner
前面討論的幾個Runner對於執行任務的型別都有比較強的限制。Platform Runner過載可能導致系統WatchDog強殺,UI和GPU Runner過載則可能導致Flutter應用的卡頓。但是GPU執行緒有一些必要操作是比較耗時間的,比如IO,而這些操作正是IO Runner需要處理的。
IO Runner的主要功能是從圖片儲存(比如磁碟)中讀取壓縮的圖片格式,將圖片資料進行處理為GPU Runner的渲染做好準備。在Texture的準備過程中,IO Runner首先要讀取壓縮的圖片二進位制資料(比如PNG,JPEG),將其解壓轉換成GPU能夠處理的格式然後將資料上傳到GPU。這些複雜操作如果跑在GPU執行緒的話會導致Flutter應用UI卡頓。但是隻有GPU Runner能夠訪問GPU,所以IO Runner模組在引擎啟動的時候配置了一個特殊的Context,這個Context跟GPU Runner使用的Context在同一個ShareGroup。事實上圖片資料的讀取和解壓是可以放到一個執行緒池裡面去做的,但是這個Context的訪問只能在特定執行緒才能保證安全。這也是為什麼需要有一個專門的Runner來處理IO任務的原因。獲取諸如ui.Image
這樣的資源只有通過async call,當這個呼叫發生的時候Flutter Framework告訴IO Runner進行剛剛提到的那些圖片非同步操作。這樣GPU Runner可以使用IO Runner準備好的圖片資料而不用進行額外的操作。
使用者操作,無論是Dart Code還是Native Plugins都是沒有辦法直接訪問IO Runner。儘管Embeder可以將一些一般複雜任務排程到IO Runner,這不會直接導致Flutter應用卡頓,但是可能會導致圖片和其它一些資源載入的延遲間接影響效能。所以建議為IO Runner建立一個專用的執行緒。
各個平臺目前預設Runner執行緒實現
前面我們提到Engine Runner的執行緒可以按照實際情況進行配置,各個平臺目前有自己的實現策略。
iOS和Android
Mobile平臺上面每一個Engine例項啟動的時候會為UI,GPU,IO Runner各自建立一個新的執行緒。所有Engine例項共享同一個Platform Runner和執行緒。
Fuchsia
每一個Engine例項都為UI,GPU,IO,Platform Runner建立各自新的執行緒。
自定義配置執行緒可行方案
我們注意到Mobile平臺上面,Platform Runner和Thread是共享的。
引擎原始碼如下:
Shell::Shell(fxl::CommandLine command_line)
: command_line_(std::move(command_line)) {
FXL_DCHECK(!g_shell);
gpu_thread_.reset(new fml::Thread("gpu_thread"));
ui_thread_.reset(new fml::Thread("ui_thread"));
io_thread_.reset(new fml::Thread("io_thread"));
// Since we are not using fml::Thread, we need to initialize the message loop
// manually.
fml::MessageLoop::EnsureInitializedForCurrentThread();
blink::Threads threads(fml::MessageLoop::GetCurrent().GetTaskRunner(),
gpu_thread_->GetTaskRunner(),
ui_thread_->GetTaskRunner(),
io_thread_->GetTaskRunner());
blink::Threads::Set(threads);
blink::Threads::Gpu()->PostTask([this]() { InitGpuThread(); });
blink::Threads::UI()->PostTask([this]() { InitUIThread(); });
blink::SetRegisterNativeServiceProtocolExtensionHook(
PlatformViewServiceProtocol::RegisterHook);
}
這裡我們可以進行改動,讓引擎每個例項初始化獨自的執行緒:
gpu_thread_.reset(new fml::Thread("gpu_thread"));
ui_thread_.reset(new fml::Thread("ui_thread"));
io_thread_.reset(new fml::Thread("io_thread"));
platform_thread_.reset(new fml::Thread("platform_thread"));
blink::Threads threads(platform_thread_->GetTaskRunner(),
gpu_thread_->GetTaskRunner(),
ui_thread_->GetTaskRunner(),
io_thread_->GetTaskRunner());
理論上你可以配置任意執行緒供其使用,不過最好遵循最佳實踐。
具體程式碼導讀
iOS Android平臺可以參考Flutter Engine原始碼:
flutter/common/threads.cc
flutter/shell/common/shell.cc
Dart isolate機制
An isolated Dart execution context. 這是文件對isolate的定義。
isolate定義
isolate是Dart對actor併發模式的實現。執行中的Dart程式由一個或多個actor組成,這些actor也就是Dart概念裡面的isolate。isolate是有自己的記憶體和單執行緒控制的執行實體。isolate本身的意思是“隔離”,因為isolate之間的記憶體在邏輯上是隔離的。isolate中的程式碼是按順序執行的,任何Dart程式的併發都是執行多個isolate的結果。因為Dart沒有共享記憶體的併發,沒有競爭的可能性所以不需要鎖,也就不用擔心死鎖的問題。
isolate之間的通訊
由於isolate之間沒有共享記憶體,所以他們之間的通訊唯一方式只能是通過Port進行,而且Dart中的訊息傳遞總是非同步的。
isolate與普通執行緒的區別
我們可以看到isolate神似Thread,但實際上兩者有本質的區別。作業系統內內的執行緒之間是可以有共享記憶體的而isolate沒有,這是最為關鍵的區別。
isolate實現簡述
我們可以閱讀Dart原始碼裡面的isolate.cc檔案看看isolate的具體實現。
我們可以看到在isolate建立的時候有以下幾個主要步驟:
- 初始化isolate資料結構
- 初始化堆記憶體(Heap)
- 進入新建立的isolate,使用跟isolate一對一的執行緒執行isolate
- 配置Port
- 配置訊息處理機制(Message Handler)
- 配置Debugger,如果有必要的話
- 將isolate註冊到全域性監控器(Monitor)
我們看看isolate開始執行的主要程式碼
Thread* Isolate::ScheduleThread(bool is_mutator, bool bypass_safepoint) {
// Schedule the thread into the isolate by associating
// a `Thread` structure with it (this is done while we are holding
// the thread registry lock).
Thread* thread = NULL;
OSThread* os_thread = OSThread::Current();
if (os_thread != NULL) {
MonitorLocker ml(threads_lock(), false);
// Check to make sure we don`t already have a mutator thread.
if (is_mutator && scheduled_mutator_thread_ != NULL) {
return NULL;
}
while (!bypass_safepoint && safepoint_handler()->SafepointInProgress()) {
ml.Wait();
}
// Now get a free Thread structure.
thread = thread_registry()->GetFreeThreadLocked(this, is_mutator);
ASSERT(thread != NULL);
// Set up other values and set the TLS value.
thread->isolate_ = this;
ASSERT(heap() != NULL);
thread->heap_ = heap();
thread->set_os_thread(os_thread);
ASSERT(thread->execution_state() == Thread::kThreadInNative);
thread->set_execution_state(Thread::kThreadInVM);
thread->set_safepoint_state(0);
thread->set_vm_tag(VMTag::kVMTagId);
ASSERT(thread->no_safepoint_scope_depth() == 0);
os_thread->set_thread(thread);
if (is_mutator) {
scheduled_mutator_thread_ = thread;
if (this != Dart::vm_isolate()) {
scheduled_mutator_thread_->set_top(heap()->new_space()->top());
scheduled_mutator_thread_->set_end(heap()->new_space()->end());
}
}
Thread::SetCurrent(thread);
os_thread->EnableThreadInterrupts();
thread->ResetHighWatermark();
}
return thread;
}
我們可以看到Dart本身抽象了isolate和thread,實際上底層還是使用作業系統的提供的OSThread。
Flutter Engine Runners與Dart Isolate
有朋友看到這裡可能會問既然Flutter Engine有自己的Runner,那為何還要Dart的Isolate呢,他們之間又是什麼關係呢?
那我們還要從Runner具體的實現說起,Runner是一個抽象概念,我們可以往Runner裡面提交任務,任務被Runner放到它所在的執行緒去執行,這跟iOS GCD的執行佇列很像。我們檢視iOS Runner的實現實際上裡面是一個loop,這個loop就是CFRunloop,在iOS平臺上Runner具體實現就是CFRunloop。被提交的任務被放到CFRunloop去執行。
Dart的Isolate是Dart虛擬機器自己管理的,Flutter Engine無法直接訪問。Root Isolate通過Dart的C++呼叫能力把UI渲染相關的任務提交到UI Runner執行這樣就可以跟Flutter Engine相關模組進行互動,Flutter UI相關的任務也被提交到UI Runner也可以相應的給Isolate一些事件通知,UI Runner同時也處理來自App方面Native Plugin的任務。
所以簡單來說Dart isolate跟Flutter Runner是相互獨立的,他們通過任務排程機制相互協作。
踩坑血淚史
理解Flutter Engine的原理以及Dart虛擬機器的非同步實現,讓我們避免採坑,更加靈活高效地進行開發。
在專案應用過程我們踩過不少坑在採坑和填坑的過程中不斷學習。這裡我簡單聊其中一個具體的案例:當時我們需要把Native載入好圖片資料註冊到Engine裡面去以便生成Texture渲染,使用完資源我們需要將其移除,看起來非常清晰的邏輯竟然造成了野指標問題。後來排查到註冊的時候在一個子執行緒進行而移除卻在Platform執行緒進行,在弄清楚執行緒結構以後問題也就迎刃而解。
結語
本文我們主要討論了Flutter層面的執行緒配置管理以及Dart本身isolate的實現。在深入瞭解Flutter執行緒機制以後,我們在開發過程當中更加得心應手,同時也啟發我們如何去設計類似應用內的執行緒結構。
目前我們在探索單個Flutter Engine以元件的方式啟動,也就是多個Flutter Engine例項同時存在通過Port來進行通訊的可能方案。感興趣或者有相關經驗的朋友歡迎交流,還請不吝賜教。
簡歷投遞:guicai.gxy@alibaba-inc.com
參考資料
- https://github.com/flutter/flutter
- https://github.com/flutter/engine
- https://flutter.io/
- https://www.dartlang.org/
- 《Dart程式語言》
相關文章
- JavaScript執行緒機制與事件機制JavaScript執行緒事件
- JAVA多執行緒與鎖機制Java執行緒
- 執行緒同步機制執行緒
- java synchronize - 執行緒同步機制Java執行緒
- 執行緒鎖 -賣票機制執行緒
- 多執行緒--執行緒管理執行緒
- 執行緒同步機制-包裝類執行緒
- 多執行緒之等待通知機制執行緒
- 分析.Net裡執行緒同步機制執行緒
- 執行緒間的協作機制執行緒
- Java多執行緒之三volatile與等待通知機制示例Java執行緒
- 執行緒與多執行緒執行緒
- 深入理解 OpenMP 執行緒同步機制執行緒
- 執行緒間通訊_等待/通知機制執行緒
- 執行緒管理執行緒
- 二. 執行緒管理之執行緒池執行緒
- 一起分析執行緒的狀態及執行緒通訊機制執行緒
- 多執行緒------執行緒與程式/執行緒排程/建立執行緒執行緒
- Python並行程式設計(二):多執行緒鎖機制利用Lock與RLock實現執行緒同步Python並行行程程式設計執行緒
- 執行緒、執行緒與程式、ULT與KLT執行緒
- ConcurrentHashMap執行緒安全機制以及原始碼分析HashMap執行緒原始碼
- Java 執行緒間通訊 —— 等待 / 通知機制Java執行緒
- 簡單案例淺析JS執行緒機制JS執行緒
- js內部事件機制–單執行緒原理JS事件執行緒
- Nginx 與 PHP 的執行機制NginxPHP
- 自建執行緒管理執行緒
- python基礎執行緒-管理併發執行緒Python執行緒
- 【多執行緒總結(二)-執行緒安全與執行緒同步】執行緒
- tomcat連線處理機制和執行緒模型Tomcat執行緒模型
- ArkUI中的執行緒和看門狗機制UI執行緒
- JS執行機制--同步與非同步JS非同步
- Android程式框架:執行緒與執行緒池Android框架執行緒
- Java多執行緒學習(3)執行緒同步與執行緒通訊Java執行緒
- Java與執行緒Java執行緒
- 程序與執行緒執行緒
- 執行緒與程序執行緒
- 執行緒與程式執行緒
- 程式與執行緒執行緒