1.同步任務(wù)與異步任務(wù)
(1)同步任務(wù):在主線程上排隊(duì)執(zhí)行的任務(wù)于樟,只有前一個(gè)任務(wù)執(zhí)行完畢衬以,才能執(zhí)行下一個(gè)任務(wù)便贵;
(2)異步任務(wù):不進(jìn)入主線程而是進(jìn)入任務(wù)隊(duì)列的任務(wù)嘲碧,只有等主線程的任務(wù)執(zhí)行完畢后,任務(wù)隊(duì)列開始通知主線程椎瘟,請(qǐng)求將異步任務(wù)進(jìn)入到主線程執(zhí)行覆致;
2.瀏覽器環(huán)境與node環(huán)境的事件循環(huán)機(jī)制
(1)瀏覽器環(huán)境:在HTML5中定義的規(guī)范;
??js執(zhí)行為單線程(不考慮web worker)肺蔚,所有代碼皆在執(zhí)行線程調(diào)用棧完成執(zhí)行煌妈;當(dāng)執(zhí)行線程任務(wù)清空后才會(huì)去輪詢?nèi)∪蝿?wù)隊(duì)列中任務(wù)。
- 任務(wù)隊(duì)列
?? 瀏覽器對(duì)不同的異步操作婆排,將其添加到任務(wù)隊(duì)列的時(shí)機(jī)也不同—由瀏覽器內(nèi)核的webcore來執(zhí)行声旺,其包含3種webAPI:- DOM Binding:處理DOM綁定事件,若綁定事件觸發(fā)時(shí)段只,回調(diào)函數(shù)立即被webcore添加到任務(wù)隊(duì)列中腮猖;
- network:處理ajax請(qǐng)求,在網(wǎng)絡(luò)請(qǐng)求返回時(shí)赞枕,才將對(duì)應(yīng)的回調(diào)函數(shù)添加到隊(duì)列中澈缺;
- timer:對(duì)setTimeout等計(jì)時(shí)器進(jìn)行延時(shí)處理,當(dāng)時(shí)間到達(dá)時(shí)才會(huì)將回調(diào)函數(shù)添加到任務(wù)隊(duì)列中炕婶;
- 異步任務(wù)類別及執(zhí)行順序
- macrotask(宏任務(wù)—task):script中代碼姐赡、setTimeout、setInterval柠掂、I/O项滑、UI render。
-
microtask(微任務(wù)): promise涯贞、Object.observe枪狂、MutationObserver。
瀏覽器異步任務(wù)執(zhí)行順序- 具體過程
(1)執(zhí)行完主執(zhí)行線程中的任務(wù)(初始執(zhí)行線程中沒有代碼宋渔,每一個(gè)script標(biāo)簽中的代碼是一個(gè)獨(dú)立的macrotask)州疾。
(2)取出Microtask Queue中任務(wù)執(zhí)行直到清空(若microtask一直被添加,則會(huì)繼續(xù)執(zhí)行microtask皇拣,卡死m(xù)acrotask)严蓖。
(3)取出Macrotask Queue中一個(gè)任務(wù)執(zhí)行。
(4)取出Microtask Queue中任務(wù)執(zhí)行直到清空氧急。
(5)重復(fù)(3)和(4)
- 具體過程
(2)node環(huán)境:由libuv庫實(shí)現(xiàn)颗胡;
??node基于事件循環(huán)實(shí)現(xiàn)非阻塞和事件驅(qū)動(dòng),其事件循環(huán)按階段執(zhí)行态蒂;
-
階段詳情
(1)timers(定時(shí)器階段):處理setTimeout()和setInterval()設(shè)定的回調(diào)函數(shù)隊(duì)列;一個(gè)timer事件指定一個(gè)下限時(shí)間而不是準(zhǔn)確的時(shí)間钾恢,在達(dá)到這個(gè)下限時(shí)間后+主線程空閑時(shí)手素,執(zhí)行該事件對(duì)應(yīng)的回調(diào)函數(shù)鸳址,從技術(shù)上來說,poll階段控制timers什么時(shí)候執(zhí)行泉懦,而執(zhí)行的具體位置在timers(poll階段會(huì)控制是否進(jìn)入下個(gè)timers階段)稿黍;
(2)I/O callbacks階段:執(zhí)行一些系統(tǒng)操作的回調(diào)(比如網(wǎng)絡(luò)通信的錯(cuò)誤回調(diào));
(3)idle崩哩、prepare:僅供libuv內(nèi)部調(diào)用巡球;
(4)poll(輪詢階段): 等待還未返回的I/O事件,任何異步方法(除timers邓嘹、setImmediate酣栈、close外)完成時(shí),都會(huì)將其加到poll queue里汹押,并立即執(zhí)行矿筝;
-
主要功能:
(i) 處理poll隊(duì)列里的事件;
(ii)執(zhí)行下限時(shí)間已經(jīng)達(dá)到的timers的回調(diào)(進(jìn)入下一個(gè)事件循環(huán)) 棚贾; 當(dāng)事件循環(huán)進(jìn)入poll階段:
(i)poll隊(duì)列不為空的時(shí)候窖维,事件循環(huán)肯定是先遍歷隊(duì)列并同步執(zhí)行回調(diào),直到隊(duì)列清空或執(zhí)行回調(diào)數(shù)達(dá)到系統(tǒng)上限妙痹。
(ii)poll隊(duì)列為空的時(shí)候铸史,這里有兩種情況。
1)如果代碼已經(jīng)被setImmediate()設(shè)定了回調(diào)怯伊,那么事件循環(huán)直接結(jié)束poll階段進(jìn)入check階段來執(zhí)行check隊(duì)列里的回調(diào)琳轿。
2)如果代碼沒有被設(shè)定setImmediate()設(shè)定回調(diào):
* 如果有被設(shè)定的timers,那么此時(shí)事件循環(huán)會(huì)檢查timers耿芹,如果有一個(gè)或多個(gè)timers下限時(shí)間已經(jīng)到達(dá)利赋,那么事件循環(huán)將繞回timers階段,并執(zhí)行timers的有效回調(diào)隊(duì)列(進(jìn)入下一個(gè)事件循環(huán)階段了)猩系。
* 如果沒有被設(shè)定timers,這個(gè)時(shí)候事件循環(huán)是阻塞在poll階段等待回調(diào)被加入poll隊(duì)列中燥。
(5)check階段:執(zhí)行setImmediate()設(shè)定的回調(diào)寇甸;
* setImmediate()實(shí)際上是一個(gè)特殊的timer,跑在事件循環(huán)中的一個(gè)獨(dú)立的階段疗涉;它使用libuv的API來設(shè)定在:
* poll階段結(jié)束后立即執(zhí)行回調(diào)拿霉;
* poll階段空閑時(shí),不讓阻塞在poll階段直接跳到check階段執(zhí)行回調(diào)咱扣。(6)close callbacks階段:如果一個(gè)socket或handle被突然關(guān)掉(比如socket.destroy())绽淘,close事件將在這個(gè)階段被觸發(fā),否則將通過process.nextTick()觸發(fā)闹伪。
-
-
任務(wù)隊(duì)列類型
原生的libuv事件循環(huán)處理的隊(duì)列有4種主要類型:
(1)Timers Queue沪铭;
(2)I/O Queue壮池;
(3)Check Queue;
(4)Close Queue杀怠;
中間隊(duì)列有2種:
(1)Next tick隊(duì)列:process.nextTick()
(2)Other Microtasks:包括其他 microtask椰憋,如 resolved promise回調(diào);
??** Next tick隊(duì)列比Other Microtasks隊(duì)列具有更高的優(yōu)先級(jí)**赔退;不過橙依,它們都在事件循環(huán)的兩個(gè)階段之間進(jìn)行處理,也就是在結(jié)束一個(gè)階段后libuv通信回傳到上層硕旗;注:NodeJS中不同類型的事件在自己的隊(duì)列中排隊(duì)窗骑;中間隊(duì)列是只要一個(gè)階段完成,事件循環(huán)就會(huì)檢查這兩個(gè)中間隊(duì)列是否有可執(zhí)行的任務(wù)漆枚,若有則立即處理它們直到為空创译,一旦為空,事件循環(huán)將繼續(xù)到下一個(gè)階段浪读。
一次tick的流程- 具體過程
(1)清空當(dāng)前循環(huán)內(nèi)的Timers Queue昔榴,清空NextTick Queue,清空Microtask Queue碘橘。
(2)清空當(dāng)前循環(huán)內(nèi)的I/O Queue互订,清空NextTick Queue,清空Microtask Queue痘拆。
(3)清空當(dāng)前循環(huán)內(nèi)的Check Queu仰禽,清空NextTick Queue,清空Microtask Queue纺蛆。
(4)清空當(dāng)前循環(huán)內(nèi)的Close Queu吐葵,清空NextTick Queue,清空Microtask Queue桥氏。
(5)進(jìn)入下輪循環(huán)(tick)温峭;
- 具體過程
4.代碼
function sleep(time) {
let startTime = new Date()
while (new Date() - startTime < time) {}
console.log('1s over')
}
setTimeout(() => {
console.log('setTimeout - 1')
setTimeout(() => {
console.log('setTimeout - 1 - 1')
sleep(1000)
})
new Promise(resolve => resolve()).then(() => {
console.log('setTimeout - 1 - then')
new Promise(resolve => resolve()).then(() => {
console.log('setTimeout - 1 - then - then')
})
})
sleep(1000)
})
setTimeout(() => {
console.log('setTimeout - 2')
setTimeout(() => {
console.log('setTimeout - 2 - 1')
sleep(1000)
})
new Promise(resolve => resolve()).then(() => {
console.log('setTimeout - 2 - then')
new Promise(resolve => resolve()).then(() => {
console.log('setTimeout - 2 - then - then')
})
})
sleep(1000)
})
6.Node的異步I/O模型
(1)基本要素:事件循環(huán)、觀察者字支、請(qǐng)求對(duì)象凤藏、IO線程池;
-
事件循環(huán):典型的生產(chǎn)者/消費(fèi)者模型堕伪;
- 事件的產(chǎn)生:網(wǎng)絡(luò)請(qǐng)求揖庄、文件IO等操作;
- 事件的消費(fèi):主線程空閑時(shí)從觀察者那兒取出事件并處理其回調(diào)欠雌;
-
觀察者:在每個(gè)Tick的過程中蹄梢,通過觀察者判斷是否有事件需要處理;
小劇場: * 主線程:飯館的廚房富俄; * 觀察者:收銀臺(tái)的小妹禁炒; * 事件及回調(diào)函數(shù):客人的點(diǎn)單而咆; * 劇情:廚房一輪一輪炒菜,但是具體要炒什么菜取決于收銀臺(tái)收到的客人的下單齐苛。
-
請(qǐng)求對(duì)象: 從js發(fā)起回調(diào)到內(nèi)核執(zhí)行完IO操作的過渡過程中的中間產(chǎn)物翘盖;
以fs.open(path,flags,[mode],callback)打開某個(gè)文件為例:從js調(diào)用Node的核心模塊->核心模塊調(diào)用C++內(nèi)建模塊->內(nèi)建模塊調(diào)用libuv進(jìn)行系統(tǒng)調(diào)用:uv_fs_open(): (1)創(chuàng)建一個(gè)FSReqWrap請(qǐng)求對(duì)象:封裝js層傳入的參數(shù)和open()方法,將回調(diào)函數(shù)設(shè)置到該對(duì)象的oncomplete_sym屬性上; (2)將這個(gè)請(qǐng)求對(duì)象推入線程池中等待執(zhí)行:當(dāng)線程池有可用線程時(shí)凹蜂,調(diào)用相應(yīng)的底層函數(shù):fs_open()馍驯; js調(diào)用完后立即返回,js線程可以繼續(xù)執(zhí)行當(dāng)前任務(wù)的后續(xù)操作玛痊,當(dāng)前的I/O操作在線程池中等待操作汰瘫,不影響js線程。
- 執(zhí)行回調(diào):以windows平臺(tái)為例
- 線程池中的I/O操作調(diào)用完后擂煞,會(huì)將獲取的結(jié)果存儲(chǔ)到req->result屬性上混弥,調(diào)用PostQueuedCompletionStatus()向IOCP(IO完成端口)提交執(zhí)行狀態(tài),告知當(dāng)前對(duì)象操作已經(jīng)完成对省,并將線程歸還線程池蝗拿;
- I/O觀察者在每次Tick的執(zhí)行中,調(diào)用GetQueuedCompletionStatus()檢查線程池是否有執(zhí)行完的請(qǐng)求蒿涎,若存在哀托,將請(qǐng)求對(duì)象加入到I/O觀察者隊(duì)列中,然后將其當(dāng)作事件處理:取出請(qǐng)求對(duì)象的result屬性做參數(shù)劳秋,取出oncomplete_sym屬性做方法仓手,然后調(diào)用執(zhí)行。
(2)基本流程
- 第一階段:組裝好對(duì)象 -> 送入IO線程池等待執(zhí)行玻淑;
-
第二階段:回調(diào)通知嗽冒;
node中整個(gè)異步IO的流程
[參考文獻(xiàn)]
1.NodeJS事件循環(huán)(英文版/中文版)
2.node中的Event模塊
3.瀏覽器和Node不同的事件循環(huán)(Event Loop)
4.《深入淺出nodejs》