## Flutter
## 單/多執行緒之謎?
---
<!-- .slide: data-background="#000000" -->
# <font color="#f00">友情警告</font>
接下來你可能會聽到一系列不正確的英文發音,
請自行考證正確發音,切勿直接對外人使用!
還有以下的所有 Example Code 皆是概念,
請勿直接複製使用,將會無法正常運行!
---
### 是 Multi-Thread?<br>還是Single-Thread?
###### 答案是 Single-Thread<!-- .element: class="fragment" data-fragment-index="0" -->
###### 但 Multi-Thread 可以做到的,它也可以!<!-- .element: class="fragment" data-fragment-index="1" -->
Note:
沒有 Thread 但有 Isolate
---
## 現在我們先思考一下
#### Single Thread 會造成什麼問題?
無法有效利用多核 CPU<!-- .element: class="fragment" data-fragment-index="0" -->
會產生 Blocking 問題<!-- .element: class="fragment" data-fragment-index="2" -->
---
#### 講到這邊你有沒有想起誰?
#### 前端唯一霸主 JavaScript<!-- .element: class="fragment" data-fragment-index="0" -->
<br>
#### JS 也是 Single-Thread<!-- .element: class="fragment" data-fragment-index="1" -->
#### 但似乎沒有 Blocking 的問題?<!-- .element: class="fragment" data-fragment-index="1" -->
Note:
雖然 Flutter 支援 Web 平台,但實作方式是把 Dart 編譯成 Javascript
----
#### 因為 JS 有一個東西叫做 Promise
#### Flutter 也有一個叫做 Future<!-- .element: class="fragment" data-fragment-index="0" -->
<br>
這兩個是幾乎一樣的東西...<!-- .element: class="fragment" data-fragment-index="1" -->
Note:
在沒有 Promise 的年代靠 callback
callback -> promise -> async/await(語法糖)
----
#### 我先來看一下簡單的 Future 範例
``` dart
main() {
printOne();
printTwo();
}
printOne() {
print(1);
}
printTwo() {
future((){
print(3);
})
print(2);
}
```
#### Output <!-- .element: class="fragment" data-fragment-index="0" -->
``` dart
1
2
3
```
<!-- .element: class="fragment" data-fragment-index="0" -->
----
##### Async / Await
Flutter 一樣提供了這兩個語法糖
讓你快速把 function 包裝成 Future
``` dart
getTestString() async {
return 'is test'
}
main()
{
String text = getTestString();
}
```
<!-- .element: class="fragment" data-fragment-index="1" -->
``` dart
String <= getTestString() {}
Future<String> <= getTestString() async {}
```
<!-- .element: class="fragment" data-fragment-index="2" -->
---
## 那麼問題來了
#### 明明是單執行緒語言,那為什麼可以異步執行?
<br>
## Event Loop<!-- .element: class="fragment" data-fragment-index="0" -->
一個讓單執行緒語言可以避免阻塞的機制<!-- .element: class="fragment" data-fragment-index="1" -->
----

Note:
這張圖是 Flutter 的,JS 架構有些不一樣。
JS 流程是每執行一個 Task 後會去執行所有 MicroTask 最後才渲染。
Flutter 因為實作了這套機制,所以大部分情況下都可以用單執行緒完成運算,所以才被定義成單執行緒語言。
----
## Event Loop 並非萬能!
雖然透過異步呼叫的方式來執行,
但如果運算太耗時一樣會造成 Blocking <!-- .element: class="fragment" data-fragment-index="0" -->
----
#### 現在我們先假定你有一台非常爛的電腦
#### 運算 SUM(1000) 需要三秒這麼久
----
#### 然後讓我們來看一下這範例
``` javascript
sum(n) {
result = 0;
for(i = 1; i <= n; i++) {
result += i;
}
return result;
}
main() {
print(1);
print(sum(1000));
print(2);
}
```
``` dart
1
等待三秒後...
500500
2
```
<!-- .element: class="fragment" data-fragment-index="0" -->
----
#### 所以我們會這樣改
``` javascript
sum(n) async {
result = 0;
for(i = 1; i <= n; i++) {
result += i;
}
return result;
}
main() {
print(1);
sum(1000).then(s => print(s));
print(2);
}
```
``` output
1
2
等待三秒後...
500500
```
<!-- .element: class="fragment" data-fragment-index="0" -->
## <font color="#f00">沒有!!!</font><!-- .element: class="fragment" data-fragment-index="1" -->
----
### 事實上問題並沒有被解決
透過異步呼叫也僅僅是改變了系統的執行順序,
運算本身還是消耗了三秒。
<br>
這三秒之間系統將無法執行其他事情
用戶的 UI 互動或事件執行將被<font color="#f00">`阻塞`</font>
<!-- .element: class="fragment" data-fragment-index="0" -->
----
#### 所以我們必須做任務拆分
``` javascript
splitSum(n, m) async {
result = 0;
for(i = n; i <= m; i++) {
result += i;
}
return result;
}
```
``` javascript
sum(n) async {
result = 0;
for (i = 1; i <= (n / 100).ceil(); i++){
start = 1 + (100 * (i -1));
end = 100 * i > n ? n : 100 * i;
result += await splitSum(start, end);
}
return result;
}
```
----
### 但事實上並沒有這麼簡單
有些運算是無法拆分的<!-- .element: class="fragment" data-fragment-index="0" -->
就算能拆分也存在一定的效能問題<!-- .element: class="fragment" data-fragment-index="1" -->
---
### 書到用時方恨少,
### 事非經過不知難。
Note:
剛開始因為 Event Loop 嚐到甜頭,但越後面效能越來越吃重,才發現光有異步呼叫是不夠的!
----
#### 現在 Event Loop 已經滿足不了我了,
#### 但是 Flutter 並沒有提供 Thread 怎麼辦?
<br>
## <span>Flutter 提供了 <font color="#ff0000">Isolate</font></span><!-- .element: class="fragment" data-fragment-index="0" -->
----
## 什麼是 Isolate ?
一個重新包裝過的 Thread<!-- .element: class="fragment" data-fragment-index="0" -->
一個 Actor Model 的實作<!-- .element: class="fragment" data-fragment-index="1" -->
Note:
Actor Model 是一種多線程的模型,除此之外還有其他模型 CSP, STM, Guilds....
----
#### 講 Isolate 前,我們先聊聊 Actor Model

<!-- .element: class="fragment" data-fragment-index="0" -->
Note:
Actor Model 有幾個特點:
1. 一個 Actor 只專注一件事。
2. Actor 有自己的狀態,不與其他人共享。
3. 所有溝通都透過 Message 進行
----
### 在這樣的架構下
### 每個 Actor 只能做三件事情
1. 接收 Message 並處理<!-- .element: class="fragment" data-fragment-index="0" -->
2. 傳送 Message<!-- .element: class="fragment" data-fragment-index="1" -->
3. 建立新的 Actor<!-- .element: class="fragment" data-fragment-index="2" -->
----
### 這樣設計有什麼好處?
<span>1. 不會產生競爭條件</span><!-- .element: class="fragment" data-fragment-index="0" --><span>,所以不需要奇形怪狀的 Lock</span><!-- .element: class="fragment" data-fragment-index="1" -->
<span>2. 每個 Actor 之間不互相依賴</span><!-- .element: class="fragment" data-fragment-index="2" --><span>,沒有執行順序問題</span><!-- .element: class="fragment" data-fragment-index="3" -->
<span>3. 天生的分散式系統</span><!-- .element: class="fragment" data-fragment-index="4" --><span>,容易擴展並且本地遠程通吃</span><!-- .element: class="fragment" data-fragment-index="5" -->
Note:
Flutter 的 Isolate 並未支援遠程 Actor
----
### Actor Model 就這麼完美嗎?
#### 並非如此!<!-- .element: class="fragment" data-fragment-index="0" -->
記憶體消耗 <!-- .element: class="fragment" data-fragment-index="1" -->
資料的一致性 <!-- .element: class="fragment" data-fragment-index="2" -->
多個 Actor 之間溝通複雜性 <!-- .element: class="fragment" data-fragment-index="3" -->
Note:
1.記憶體不共享,所以傳送資料會以副本的方式進行,導致同一份資料會佔用多份記憶體
2.當有多個資料副本時,就會產生資料一致性問題
3.如果服務有多個 Actor 透過 Message 將會很複雜
---
#### 講完 Actor 後,我們再來聊點 Isolate
在 Flutter 裡本身 Main() 主程式就是一個 Isolate <!-- .element: class="fragment" data-fragment-index="0" -->
而每個 Isolate 都只會由一個 Thead 負責 <!-- .element: class="fragment" data-fragment-index="1" -->
Note:
這也是 Flutter 被稱為單執行緒語言的原因之一
----
#### 想使用 Isolate 前,你必須先這知道三樣東西
* Isolate
* ReceivePort
* SendPort
Note:
Isolate 本身就是負責運算 method
ReceivePort 及 SendPort 是一對的,負責傳送訊息溝通
----
#### 接下來我們來了解如何建立 Isolate
#### 方法有以下兩種
<font color="#ff0000">Spawn<!-- .element: class="fragment" data-fragment-index="0" --></font><span> 與 <!-- .element: class="fragment" data-fragment-index="1" --></span><font color="#ff0000">SpawnUrl<!-- .element: class="fragment" data-fragment-index="1" --></font>
----
#### 我們先來看 Spawn
``` javascript
sum(Array data) {
SendPort sendPort = data[0];
result = 0;
for(i = 1; i <= data[1]; i++) {
result += i;
}
sendPort.send(result);
}
```
``` javascript
main() {
print(1);
ReceivePort receivePort = ReceivePort();
SendPort sendPort = receivePort.sendPort
isolate = await Isolate.spawn(sum, [sendPort, 1000]);
receivePort.listen((result) {
print(result);
});
print(2);
}
```
----
#### 以為這樣就結束了嗎??
#### 沒有!!!<!-- .element: class="fragment" data-fragment-index="0" -->
你還要自己 Kill Isolate<!-- .element: class="fragment" data-fragment-index="1" -->
``` javascript
isolate.kill(priority: Isolate.immediate);
```
<!-- .element: class="fragment" data-fragment-index="1" -->
Note:
Kill 優先級分為
immediate 立即刪除
BeforeNextEvent 下次循環 Event 時刪除
----
#### 可是你說我只是運算一次而已,
#### 每次還要建立跟管理一堆 ReceivePort 與 SendPort, <!-- .element: class="fragment" data-fragment-index="1" -->
#### 還要自己 Kill 也太麻煩了吧?<!-- .element: class="fragment" data-fragment-index="2" -->
----
#### 你的聲音 Flutter 聽到了
#### `Compute`重新包裝了 Isolate<!-- .element: class="fragment" data-fragment-index="1" -->
現在你只要專注與 Isolate 需要運算什麼就好了<!-- .element: class="fragment" data-fragment-index="2" -->
----
#### 按照慣例我們在搬出那台很爛的電腦
``` javascript
sum(n) {
result = 0;
for(i = 1; i <= n; i++) {
result += i;
}
return n;
}
```
``` javascript
main() {
print(1);
compute(sum, 1000).then((result) {
print(result)
});
print(2)
}
```
Note:
Compute 是利用 async 去重新包裝了 Isolate,
當你呼叫 Compute 時,他會去建立一個 Isolate 再把你的 method 丟進去,然後用一個 Future 包裝起來,等 Isolate 回應後就自動 Kill 掉,在執行 then
----
### Compute 簡化了 Isolate 溝通成本
<br>
但也失去互相溝通的機制,
所以只適合拿來用在單次運算的地方
<!-- .element: class="fragment" data-fragment-index="1" -->
Note:
Comput 適合單次運算,例如加解密
Isolate 適合持續性的運算,例如 LogIsolate 一直收集 Log 並記錄
---
## Future 及 Isolate 使用時機
<br>
基本上大部分的情況都可以使用 Future 解決,<!-- .element: class="fragment" data-fragment-index="0" -->
因為前端性質的服務很少會有耗時的操作,<!-- .element: class="fragment" data-fragment-index="1" -->
大部分耗時操作都是由外部 API 幫你執行,<!-- .element: class="fragment" data-fragment-index="2" -->
所以只有當你有很奇耙的運算,
才需要搬出 Isolate 來坐鎮<!-- .element: class="fragment" data-fragment-index="3" -->
Note:
這也是為什麼 Flutter 使用 Event Loop 機制,而不是直接使用多執行緒機制的原因之一
---
#### 這次主要是分享一些單多執行緒的差異
#### 所以前面很多提到的都只是觀念跟運作方式
#### 事實上在使用的時候還有很多眉角跟限制
#### 不過不在這次討論內容,有興趣的可以自行查閱
<br>
#### 最後感謝大家的收聽
<div style="font-size: 12px">
以上圖片及資料都來自網路資料
</div>
{"metaMigratedAt":"2023-06-15T05:52:54.715Z","metaMigratedFrom":"YAML","title":"Flutter 單/多執行緒之謎","breaks":true,"slideOptions":"{\"progress\":true,\"theme\":\"solarized\",\"transition\":\"concave\",\"spotlight\":{\"enabled\":true}}","contributors":"[{\"id\":\"6cafde5d-0be9-4081-bb7a-2b953ef414d1\",\"add\":16215,\"del\":6574}]"}