Dart 單線程才漆、Isolate

Dart 單線程模型

眾所周知,在Java中使用多線程來處理并發(fā)任務佛点,適量并合適地使用多線程醇滥,能夠極大地提高資源的利用率和程序運行效率黎比,但是缺點也比較明顯,比如過度開啟線程會帶來額外的資源和性能消耗或多線程共享內(nèi)存容易出現(xiàn)死鎖等鸳玩。實際上阅虫,在APP的使用過程中,多數(shù)處理空閑狀態(tài)不跟,并不需要進行密集或高并發(fā)的處理颓帝,因此從某些意義上來說,多線程顯得有點多余窝革。正是因為如此购城,Dart作為一種新的語言,通過引單線程模型很好地處理了并發(fā)任務對多線程的依賴虐译。

1.1 單線程模型

Dart是一種單線程語言瘪板,因此Dart程序沒有主線程和子線程之分,而在Dart中線程并不是指Thread漆诽,而是指Isolate侮攀。因為Dart沒有線程的概念,只有Isolate厢拭,每個Isolate都是隔離的兰英,并不會共享內(nèi)存。所有的Dart代碼都是在Isolate中運行供鸠,它就像機器上的一個小空間畦贸,具有自己的私有內(nèi)存塊和一個運行著事件循環(huán)模型的單線程。也就是說回季,一旦某個Dart函數(shù)開始執(zhí)行家制,它將執(zhí)行到這個函數(shù)的結(jié)束而不被其他Dart代碼打斷,這就是單線程的特性泡一。

默認情況下颤殴,Dart程序只有一個Isolate(未自己創(chuàng)建的情況下),而這個Isolate就是Main Isolate鼻忠。也就是說涵但,一個Dart程序是從Main Isolate的main函數(shù)開始的,而在main函數(shù)結(jié)束后帖蔓,Main isolate線程開始一個一個處理事件循環(huán)模型隊列中的每一事件(Event)矮瘟。上圖描述的就是Main Isolate的消息循環(huán)模型。

1.2 事件循環(huán)模型

也許你會問塑娇,既然Dart是一種單線程語言澈侠,那么是不是就意味著Dart無法并發(fā)處理異步任務了?此言差矣埋酬。前面說到哨啃,所有的Dart程序都在Isolate中運行烧栋,每個Isolate擁有自己的私有內(nèi)存塊和一個事件循環(huán)模型,其中拳球,事件循環(huán)模型就是用來處理各種事件审姓,比如點輸入/輸出,點擊祝峻,定時器以及異步任務等魔吐。下圖描述了一個Isolate的事件循環(huán)模型的整個流程:

25165435_623d834b4b79d88513.png

從上圖可知,Dart事件循環(huán)機制由一個消息循環(huán)(event looper)和兩個消息隊列構(gòu)成莱找,其中酬姆,兩個消息隊列是指事件隊列(event queue)和微任務隊列(Microtask queue)。該機制運行原理為:

  • 首先宋距,Dart程序從main函數(shù)開始運行轴踱,待main函數(shù)執(zhí)行完畢后,event looper開始工作谚赎;
  • 然后淫僻,event looper優(yōu)先遍歷執(zhí)行Microtask隊列所有事件,直到Microtask隊列為空壶唤;
  • 接著雳灵,event looper才遍歷執(zhí)行Event隊列中的所有事件,直到Event隊列為空闸盔;
  • 最后悯辙,視情況退出循環(huán)。

為了進一步理解迎吵,我們解釋下上述三個概念:

(1)消息循環(huán)(Event Looper)

顧名思義躲撰,消息循環(huán)就是指一個永不停歇且不能阻塞的循環(huán),它將不停的嘗試從微任務隊列和事件隊列中獲取事件(event)進行處理击费,而這些Event包括了用戶輸入拢蛋,點擊,Timer蔫巩,文件IO等谆棱。

25165435_623d834b58a3510512.png

(2)事件隊列(Event queue)

該隊列的事件來源于外部事件和Future,其中圆仔,外部事件主要包括I/O垃瞧,手勢,繪制坪郭,計時器和isolate相互通信的message等个从,而Future主要是指用戶自定義的異步任務,通過創(chuàng)建Future類實例來向事件隊列添加事件歪沃。需要注意的是嗦锐,當Event looper正在處理Microtask Queue時鸵隧,Event queue會被阻塞,此時APP將無法進行UI繪制意推,響應用戶輸入和I/O等事件。下列示例演示了向Event queue中添加一個異步任務事件:

main(List<String> args) {
  print('main start...')
  
  var futureInstance = Future<String>(() => "12345");
  futureInstance.then((res) {
    print(res);
  }).catchError((err) {
    print(err);
  });
  
  print('main end...')
}

// 打印結(jié)果:
//      main start...
//      main end...
//      12345

(3)微任務隊列(Microtask queue)

該隊列的事件來源與當前isolate的內(nèi)部或通過scheduleMicrotask函數(shù)創(chuàng)建珊蟀,Microtask一般用于非常短的內(nèi)部異步動作菊值,并且任務量非常少,如果微任務非常多育灸,就會造成Event queue排不上隊腻窒,會阻塞Event queue的執(zhí)行造成應用ANR,因為Microtask queue的優(yōu)先級高于Event queue磅崭。因此儿子,大多數(shù)情況下的任務優(yōu)先考慮使用Event queue,不到萬不得已不要使用Microtask queue砸喻。下列 示例演示了兩個事件隊列執(zhí)行情況:

iimport 'dart:async';

void main(List<String> args) {
  print('main start...');

  /// 創(chuàng)建異步event queue
  var futureInstance = Future<String>(() => "12345");
  futureInstance.then((res) {
    print(res);
  }).catchError((err) {
    print(err);
  });

  /// 執(zhí)行函數(shù)
  stark();

  /// scheduleMicrotask函數(shù)創(chuàng)建微任務MircroTask queue
  scheduleMicrotask(() => print('microtask #1 of 2'));
  print('main end...');

  /// scheduleMicrotask函數(shù)創(chuàng)建微任務MircroTask queue
  scheduleMicrotask(() => print('microtask #2 of 2'));
}

void stark() async {
  print('stark start...');

  /// 創(chuàng)建異步event queue
  var futureInstance = Future<String>(() => "900303");

  /// 異步等待
  var rep = await futureInstance;
  print(rep);

  /// 創(chuàng)建異步event queue
  var futureInstance1 = Future<String>(() => "xxxxx");
  futureInstance1.then((res) {
    print(res);
  }).catchError((err) {
    print(err);
  });

  print('stark end...');
}

// 執(zhí)行順序:
// main start...
// stark start...
// main end...
// microtask #1 of 2
// microtask #2 of 2
// 12345
// 900303
// stark end...
// xxxxx

1.3 Isolate

大多數(shù)計算機中柔逼,甚至在移動平臺上,都在使用多核CPU割岛。 為了有效利用多核性能愉适,開發(fā)者一般使用共享內(nèi)存數(shù)據(jù)來保證多線程的正確執(zhí)行。 然而多線程共享數(shù)據(jù)通常會導致很多潛在的問題癣漆,并導致代碼運行出錯维咸。Dart作為一種新語言,為了緩解上述問題惠爽,提出了Isolate(隔離區(qū))的概念癌蓖,即Dart沒有線程的概念,只有Isolate婚肆,所有的Dart代碼都是在Isolate中運行租副,它就像是機器上的一個小空間,具有自己的私有內(nèi)存堆和一個運行著Event Looper的單個線程旬痹。

通常附井,一個Dart應用對應著一個Main Isolate,且應用的入口即為該Isolate的main函數(shù)两残。當然永毅,我們也可以創(chuàng)建其它的Isolate,由于Isolate的內(nèi)存堆是私有的人弓,因此這些Isolate的內(nèi)存都不會被其它Isolate訪問沼死。假如不同的Isolate需要通信(單向/雙向),就只能通過向?qū)Ψ降氖录h(huán)隊列里寫入任務崔赌,并且它們之間的通訊方式是通過port(端口)實現(xiàn)的意蛀,其中耸别,Port又分為receivePort(接收端口)和sendPort(發(fā)送端口),它們是成對出現(xiàn)的县钥。Isolate之間通信過程:

  • 首先秀姐,當前Isolate創(chuàng)建一個ReceivePort對象,并獲得對應的SendPort對象若贮;
var receivePort = ReceivePort();
var sendPort = receivePort.sendPort;
  • 其次省有,創(chuàng)建一個新的Isolate,并實現(xiàn)新Isolate要執(zhí)行的異步任務谴麦,同時蠢沿,將當前Isolate的SendPort對象傳遞給新的Isolate,以便新Isolate使用這個SendPort對象向原來的Isolate發(fā)送事件匾效;
// 調(diào)用Isolate.spawn創(chuàng)建一個新的Isolate
// 這是一個異步操作舷蟀,因此使用await等待執(zhí)行完畢
var anotherIsolate = await Isolate.spawn(otherIsolateInit, receivePort.sendPort);

// 新Isolate要執(zhí)行的異步任務
// 即調(diào)用當前Isolate的sendPort向其receivePort發(fā)送消息
void otherIsolateInit(SendPort sendPort) async {
  value = "Other Thread!";
  sendPort.send("BB");
}
  • 第三,調(diào)用當前Isolate#receivePort的listen方法監(jiān)聽新的Isolate傳遞過來的數(shù)據(jù)面哼。Isolate之間什么數(shù)據(jù)類型都可以傳遞野宜,不必做任何標記。
receivePort.listen((date) {
    print("Isolate 1 接受消息:data = $date");
});
  • 最后魔策,消息傳遞完畢速缨,關閉新創(chuàng)建的Isolate。

import 'dart:isolate';

var anotherIsolate;
var value = "Now Thread!";

void startOtherIsolate() async {
  value = " Isolate1 Thread!";

  /// 獲取當前 Isolate 接收對象
  var receivePort = ReceivePort();

  receivePort.listen((message) {
    if (message is String) {
      print("Isolate1 接受消息:data = $message; value = $value");
    } else if (message is SendPort) {
      message.send("我來自 isolate1 發(fā)送 消息");
    }
  });

  /// 創(chuàng)建 Isolate 對象
  anotherIsolate = await Isolate.spawn(otherIsolateInit, receivePort.sendPort);
}

void otherIsolateInit(SendPort sendPort) async {
  value = " Isolate2 Thread!";

  /// 獲取當前 Isolate 接收對象
  var receivePort2 = ReceivePort();
  receivePort2.listen((message) {
    print("Isolate2 接受消息:message = $message; value = $value ");
  });

  /// 發(fā)送消息
  sendPort.send("加油");
  sendPort.send(receivePort2.sendPort);
}

void main(List<String> args) {
  startOtherIsolate();
}
==============執(zhí)行結(jié)果===========

Isolate1 接受消息:data = 加油; value =  Isolate1 Thread!
Isolate2 接受消息:message = 我來自 isolate1 發(fā)送 消息; value =  Isolate2 Thread! 

Isolate 處理耗時操作使用場景

import 'dart:isolate';

var anotherIsolate;
var value = "Now Thread!";

void startOtherIsolate() async {
  value = " Isolate1 Thread!";
  int count = 1000000000;

  /// 獲取當前 Isolate 接收對象
  var receivePort = ReceivePort();

  receivePort.listen((message) {
    if (message is String) {
      print("Isolate1 接收消息:message = $message; value = $value");
    } else if (message is SendPort) {
      message.send("我來自 isolate1 發(fā)送 消息");
    } else if (message is int) {
      print('Isolate1 接接int消息: $message');
    }
  });

  /// 創(chuàng)建 Isolate 對象
  anotherIsolate = await Isolate.spawn(
      otherIsolateInit, {"sendPort": receivePort.sendPort, "params": count});
}

void otherIsolateInit(Map<String, dynamic> info) async {
  SendPort sendPort = info['sendPort'];
  int count = info['params'];
  value = " Isolate2 Thread!";
  print('otherIsolateInit start...');

  /// 獲取當前 Isolate 接收對象
  var receivePort2 = ReceivePort();
  receivePort2.listen((message) {
    print("Isolate2 接受消息:message = $message; value = $value ");
  });

  /// 發(fā)送消息
  sendPort.send(receivePort2.sendPort);

  /// 處理耗時任務
  /// 耗時任務會阻塞該isolate代乃、不會阻塞main isolate
  int _sum = 0;
  int _index = 0;
  while (_index <= count) {
    _sum += _index;
    _index++;
  }
  sendPort.send(_sum);

  print('otherIsolateInit end...');
}

void main(List<String> args) {
  startOtherIsolate();
}

============輸入日志===============

otherIsolateInit start...
otherIsolateInit end...
Isolate1 接接int消息: 500000000500000000
Isolate2 接受消息:message = 我來自 isolate1 發(fā)送 消息; value =  Isolate2 Thread! 

轉(zhuǎn)載原文

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末旬牲,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子搁吓,更是在濱河造成了極大的恐慌原茅,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件堕仔,死亡現(xiàn)場離奇詭異擂橘,居然都是意外死亡,警方通過查閱死者的電腦和手機摩骨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門通贞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人恼五,你說我怎么就攤上這事昌罩。” “怎么了灾馒?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵茎用,是天一觀的道長。 經(jīng)常有香客問我,道長轨功,這世上最難降的妖魔是什么旭斥? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮古涧,結(jié)果婚禮上垂券,老公的妹妹穿的比我還像新娘。我一直安慰自己羡滑,他們只是感情好圆米,可當我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著啄栓,像睡著了一般。 火紅的嫁衣襯著肌膚如雪也祠。 梳的紋絲不亂的頭發(fā)上昙楚,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天,我揣著相機與錄音诈嘿,去河邊找鬼堪旧。 笑死,一個胖子當著我的面吹牛奖亚,可吹牛的內(nèi)容都是我干的淳梦。 我是一名探鬼主播,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼昔字,長吁一口氣:“原來是場噩夢啊……” “哼爆袍!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起作郭,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤陨囊,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后夹攒,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蜘醋,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年卵慰,在試婚紗的時候發(fā)現(xiàn)自己被綠了格侯。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片榄鉴。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖胎食,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情允懂,我是刑警寧澤斥季,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響酣倾,放射性物質(zhì)發(fā)生泄漏舵揭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一躁锡、第九天 我趴在偏房一處隱蔽的房頂上張望午绳。 院中可真熱鬧,春花似錦映之、人聲如沸拦焚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赎败。三九已至,卻和暖如春蠢甲,著一層夾襖步出監(jiān)牢的瞬間僵刮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工鹦牛, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留搞糕,地道東北人。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓曼追,卻偏偏與公主長得像窍仰,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子礼殊,可洞房花燭夜當晚...
    茶點故事閱讀 42,834評論 2 345