成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

JavaScript單線程事件循環(huán)(Event Loop)那些事

Shisui / 1899人閱讀

摘要:概述本篇主要介紹的運(yùn)行機(jī)制單線程事件循環(huán)結(jié)論先在中利用運(yùn)行至完成和非阻塞完成單線程下異步任務(wù)的處理就是先處理主模塊主線程上的同步任務(wù)再處理異步任務(wù)異步任務(wù)使用事件循環(huán)機(jī)制完成調(diào)度涉及的內(nèi)容有單線程事件循環(huán)同步執(zhí)行異步執(zhí)行定時器的事件循環(huán)開始

1.概述

本篇主要介紹JavaScript的運(yùn)行機(jī)制:單線程事件循環(huán)(Event Loop).

結(jié)論先: 在JavaScript中, 利用運(yùn)行至完成和非阻塞IO 完成單線程下異步任務(wù)的處理. 就是先處理主模塊(主線程)上的同步任務(wù), 再處理異步任務(wù). 異步任務(wù)使用事件循環(huán)機(jī)制完成調(diào)度.

涉及的內(nèi)容有: 單線程, 事件循環(huán), 同步執(zhí)行, 異步執(zhí)行, 定時器, nodeJS的事件循環(huán)

開始之前, 先看下面的代碼, 給出結(jié)果:

// 當(dāng)前時間
console.log("A: " + new Date());

// 1秒(1000毫秒)后執(zhí)行的定時器
// 異步執(zhí)行的代碼
setTimeout(function() {
    console.log("B: " + new Date());
}, 1000);

// 循環(huán)3秒(3000毫秒)
var end = Date.now() + 3000;
while(Date.now() < end) {
}

// 當(dāng)前時間
console.log("C: " + new Date());

在瀏覽器中的結(jié)果為(chrome-50.0.2661.102):

A: Thu May 25 2017 13:48:26 GMT+0800 (中國標(biāo)準(zhǔn)時間)
C: Thu May 25 2017 13:48:29 GMT+0800 (中國標(biāo)準(zhǔn)時間) 
B: Thu May 25 2017 13:48:29 GMT+0800 (中國標(biāo)準(zhǔn)時間)

在NodeJS(v7.7.2 win-x64)中的結(jié)果為:

>node scriptsasync.js
A: Thu May 25 2017 13:50:55 GMT+0800 (中國標(biāo)準(zhǔn)時間)
C: Thu May 25 2017 13:50:58 GMT+0800 (中國標(biāo)準(zhǔn)時間)
B: Thu May 25 2017 13:50:58 GMT+0800 (中國標(biāo)準(zhǔn)時間)

tip: 瀏覽器下和NodeJS結(jié)果一致.

分析上面的代碼與結(jié)果, 注意的要點(diǎn):

雖然設(shè)置的定時器為1秒后執(zhí)行, 但實(shí)際的執(zhí)行時間在3秒以后, 看結(jié)果中B:的輸出, 在A:的3秒后.

B:的輸出在C:的輸出之后. 可見, 雖然在while循環(huán)后, 時間已經(jīng)到了定時器代碼需要執(zhí)行的時間, 但并沒有立即執(zhí)行, 而是等到了console.log("C: ")執(zhí)行完, 再執(zhí)行的定時器的代碼.

本篇就是說明為什么會出現(xiàn)以上的現(xiàn)象. 下面請一步步的看.

2.單線程

單線程, 指的是JavaScript在一個時間僅處理一個任務(wù). 就是JavaScript在執(zhí)行時, 存在一個執(zhí)行隊(duì)列, 依次執(zhí)行隊(duì)列中的任務(wù), 不能同時執(zhí)行多個任務(wù).
單線程的優(yōu)勢, 也是JavaScript選擇單線程的原因是:
1, 降低處理復(fù)雜性, 簡化開發(fā). 例如不用考慮死鎖, 競爭機(jī)制等.
2, 作為用于處理與用戶互動的腳本語言, 可以更加容易地處理狀態(tài)同步的問題(想想考慮用戶操作的不確定性).
3, JavaScript核心維護(hù)人員自身的設(shè)計(jì)與理解.
4, 越簡單越容易推廣, 快速上手.

除了優(yōu)勢, 單線程有明顯的劣勢, 就是并發(fā)處理能力, 因?yàn)閱尉€程處理下所有的任務(wù)就要排隊(duì)處理. 但是如果排在前面的任務(wù)處理很耗時, 那就導(dǎo)致后面的任務(wù)一直處于等待狀態(tài). 如果前面的任務(wù)出處于滿載運(yùn)行狀態(tài)還可以, 但是如果前面的任務(wù)處于IO等待狀態(tài)呢? 就會導(dǎo)致CPU處理資源的浪費(fèi).
思考, 前面的是AJAX任務(wù), 后邊是其他任務(wù). AJAX任務(wù)需要等待網(wǎng)絡(luò)請求響應(yīng)結(jié)束, 才能處理, 此時前面的AJAX任務(wù)就處于IO等待狀態(tài). 從而導(dǎo)致后面的任務(wù)也執(zhí)行不了, 造成了單線程下的資源浪費(fèi). (CPU沒有辦法高速運(yùn)轉(zhuǎn), 處于空閑狀態(tài)).

在此情況下, 完全可以掛起前面的AJAX任務(wù)(掛起等待AJAX的響應(yīng)結(jié)果), 先執(zhí)行后面的任務(wù). 等后面的任務(wù)處理完畢后, 再看前面的AJAX任務(wù)是否得到了IO結(jié)果, 如果有結(jié)果了, 在翻回來處理即可. 這種處理方式, 就是異步方式.

3.同步任務(wù)和異步任務(wù)

單線程的JavaScript為了更好利用CPU的性能, 將執(zhí)行的任務(wù)設(shè)計(jì)為: 同步任務(wù)和異步任務(wù), 兩類.

同步任務(wù)(synchronous task), 就是需要一個個順序執(zhí)行的任務(wù), 不能跳過, 執(zhí)行完前一個才能執(zhí)行后一個. 我們稱之為在主模塊(主線程)執(zhí)行的任務(wù).

異步任務(wù)(asynchronous task), 指的是被掛起執(zhí)行的任務(wù), 在系統(tǒng)內(nèi)部處于等待IO處理結(jié)果狀態(tài), 一旦處理完畢, 記錄下來, 等待后續(xù)處理. 需要事件循環(huán)處理的任務(wù). 上面示例中的AJAX任務(wù)就是異步任務(wù).

你應(yīng)該會想, JavaScript不是單線程么, 怎么還能異步處理呢?
是這樣的, JavaScript的單線程, 指的是在JavaScript語言(語法)層面是單線程的. 而內(nèi)部的執(zhí)行, 還是可以利用到處理器多線程和操作系統(tǒng)的任務(wù)調(diào)度的, 在后臺處理我們的異步任務(wù). 當(dāng)操作在后臺被處理完成后(例如ajax接收完畢了服務(wù)器的響應(yīng)), 操作系統(tǒng)將結(jié)果告知給JavaScript, 并最終被JavaScript執(zhí)行.

JavaScript是如何調(diào)度這些同步任務(wù)和異步任務(wù)的呢?
就涉及到了, 本文的重點(diǎn): 任務(wù)隊(duì)列 和 事件循環(huán), 執(zhí)行棧.

4.事件循環(huán)模型

如圖(邏輯概述圖)所示:

執(zhí)行如下:

step1, 同步任務(wù)直接放入到主模塊(主線程)任務(wù)隊(duì)列執(zhí)行. 異步任務(wù)掛起后臺執(zhí)行, 等待IO事件完成或行為事件被觸發(fā).

step2, 系統(tǒng)后臺執(zhí)行異步任務(wù), 如果某個異步任務(wù)事件發(fā)生(或者是行為事件被觸發(fā)), 則將該任務(wù)push到任務(wù)隊(duì)列中, 每個任務(wù)會對應(yīng)一個回調(diào)函數(shù)進(jìn)行處理. 這個步驟在后臺一直執(zhí)行, 因?yàn)榫筒粩嘤惺录挥|發(fā), IO不斷完成, 任務(wù)被不斷的加入到任務(wù)隊(duì)列中.

step3, 執(zhí)行任務(wù)隊(duì)列中的任務(wù). 任務(wù)的具體執(zhí)行是在執(zhí)行棧中完成的. 當(dāng)運(yùn)行棧中一個任務(wù)的基本運(yùn)行單元(稱之為Frame, 楨)全部執(zhí)行完畢后, 去讀取任務(wù)隊(duì)列中的下一個任務(wù), 繼續(xù)執(zhí)行. 是一個循環(huán)的過程. 處理一個任務(wù)隊(duì)列中的任務(wù), 稱之為一個tick.

注意, step3, 是一個循環(huán)的過程, 這就是事件循環(huán). 循環(huán)執(zhí)行任務(wù)隊(duì)列中已經(jīng)發(fā)生的事件對應(yīng)的任務(wù).

再參考開始的代碼, 我們可以知道:

// A:當(dāng)前時間 
// 同步代碼, 直接進(jìn)入任務(wù)隊(duì)列
console.log("A: " + new Date());

// B:1秒(1000毫秒)后執(zhí)行的定時器
// 異步代碼, 等待到時事件發(fā)生, 才會進(jìn)入任務(wù)隊(duì)列
setTimeout(function() {
    console.log("B: " + new Date());
}, 1000);

// 循環(huán)3秒(3000毫秒), 
// 同步代碼, 直接進(jìn)入任務(wù)隊(duì)列
var end = Date.now() + 3000;
// 同步代碼, 直接進(jìn)入任務(wù)隊(duì)列
while(Date.now() < end) {
}

// C:當(dāng)前時間
// 同步代碼, 直接進(jìn)入任務(wù)隊(duì)列
console.log("C: " + new Date());

也就意味著, 此時, log(A), while, log(C) 三個任務(wù), 已經(jīng)進(jìn)入到了任務(wù)隊(duì)列中. 而setTimeout是異步任務(wù)(與AJAX一致)在等待事件發(fā)生(到時事件). 于此同時, JavaScript開始處理任務(wù)隊(duì)列. 隊(duì)列是先進(jìn)先出, 需要依次處理. 所以, 即時當(dāng)前已經(jīng)到1s了, 事件發(fā)生, 也僅僅是將該任務(wù)push入任務(wù)隊(duì)列而已(并沒有立即執(zhí)行回調(diào)函數(shù)). 當(dāng)將setTimeout入隊(duì)列時, log(C)已經(jīng)在隊(duì)列中了, 因此, setTimeout的log(B), 會在log(B)后執(zhí)行. 這就是輸出了: A, C, B的原因. 如下圖(邏輯概述圖)所示:

由任務(wù)隊(duì)列可知, 輸出為: A, C, B順序.

5.定時器函數(shù)

JavaScript提供了可以操作定時器的函數(shù), setTimeout()和setInterval. 在NodeJS中還有setImmediate().

setTimeout(), 定時執(zhí)行

setTimeout(callback, timer), 多久(毫秒計(jì))后執(zhí)行, 常規(guī)用法已經(jīng)演示.
需要提醒大家的是, setTimetout()是延時觸發(fā), 而不是即時觸發(fā). 指的是, 在有機(jī)會處理計(jì)時器事件時, 優(yōu)先處理最先到時的計(jì)時器程序. 而不是時間到立即處理. 因?yàn)槭菃尉€程, 需要先處理當(dāng)前的任務(wù), 例如主模塊中的任務(wù)(同步任務(wù)).

實(shí)操中還有一個setTimeout(callback, 0)的用法, 表示立即加入到任務(wù)隊(duì)列. 但是注意, 并不是在執(zhí)行setTimeout的時候, 就加入隊(duì)列了, 而是當(dāng)全部的同步任務(wù)入隊(duì)列后, 立即加入到任務(wù)隊(duì)列, 也就意味著同步任務(wù)之后第一個執(zhí)行. 但據(jù)說這個值內(nèi)部執(zhí)行時有一個最小值, 4ms.
上面的代碼, 將時間改為0, 測試結(jié)果還是A, C, B. 不會因?yàn)橄葓?zhí)行的setTimeout()而就將任務(wù)先執(zhí)行.

// 異步代碼, 等待到時事件發(fā)生, 才會進(jìn)入任務(wù)隊(duì)列
setTimeout(function() {
    console.log("B: " + new Date());
}, 0);

未發(fā)生的定時器, 可以使用clearTimeout()方法清除.

setInterval(), 循環(huán)執(zhí)行.

setInterval(callback, timer) 與setTimeout()相似, 不過是在callback執(zhí)行完畢后, 再次設(shè)置了計(jì)時器. 不再贅述.

6.NodeJS中的事件循環(huán)

NodeJS的事件循環(huán)模型比瀏覽器更為復(fù)雜些.
如下圖所示(引用自NodeJS官方文檔), 事件循環(huán), 按照下圖的順序調(diào)用事件.

由于出現(xiàn)了不同的事件循環(huán)段, 例如 timer, check, 出現(xiàn)了額外的控制定時器方法.

setImmediate(), 立即執(zhí)行

邏輯含義上講, 與setTimeout(callback, 0)一致. 都是立即執(zhí)行. 在NodeJS中setImmediate()存在的主要場景就是, 在異步IO調(diào)用中, 如果同時使用setImmediate()和settimeout(), 可以保證, setImmediate()先于所有的setTimeout()執(zhí)行.
如下代碼: (引用自NodeJS官方文檔)

var fs = require("fs");
// 異步文件IO
fs.readFile(__filename, () => {

    setTimeout(function timeout () {
      console.log("timeout");
    },0);

    setImmediate(function immediate () {
      console.log("immediate");
    });
});

以上代碼的執(zhí)行結(jié)果, 一定是:

>node scriptsasync-node.js
immediate
timeout
這是因?yàn)? 根據(jù)NodeJS的事件循環(huán)處理順序, 處理完IO后, 需要處理check, 而setImmediate()就是check中的事件. 因此先處理.

但上面的代碼如果沒有在異步IO中調(diào)用, 在主模塊(主線程)中調(diào)用, 則順序不一定, 由操作系統(tǒng)調(diào)度決定!

process.nextTick(callback)

tick, 就是一個事件循環(huán)周期. 在prcess.nextTick()中設(shè)置的異步callback會在當(dāng)前事件循環(huán)周期結(jié)束, 下一個事件循環(huán)周期開始前執(zhí)行.
像是一個插入的tick. 生成了一個新的周期. 說白了, 是一個插隊(duì)行為.
因此, 在時間上看, 一定先于settimeout(callback, 0)和setImmediate()執(zhí)行. 通常用來處理在下一個事件周期(異步任務(wù))前, 必須要處理好的任務(wù). 常見的有, 處理錯誤, 回收資源, 和 重新執(zhí)行存在錯誤的操作等.

測試一下執(zhí)行時機(jī):

setTimeout(function timeout () {
  console.log("timeout");
},0);

setImmediate(function immediate () {
  console.log("immediate");
});

process.nextTick(function immediate () {
  console.log("nickTick");
});

結(jié)果為:

>node scriptsasync-node.js
nextTick
timeout
immediate

可見, nextTick先發(fā)生.

注意, 在NodeJS中, nexttick并不是一個特殊的定時器.
注意, 由于nextTick()會插隊(duì)執(zhí)行, 因此, NodeJS限制了nextTick()遞歸調(diào)用的深度. 防止IO處理饑餓.一直在處理nextTick(). 由于該原因, 遞歸時, NodeJS建議使用setImmediate()完成.

對比process.nextTick, setImmediate, setTimeout

process.nextTick, 永遠(yuǎn)先執(zhí)行.
setImmediate和setTimeout, 那個先到時那個先執(zhí)行. 如果同時, 則由系統(tǒng)調(diào)度負(fù)責(zé).

7.總結(jié)

在JavaScript中, 利用運(yùn)行至完成和非阻塞IO 完成單線程下異步任務(wù)的處理. 就是先處理主模塊(主線程)上的同步任務(wù), 再處理異步任務(wù). 異步任務(wù)使用事件循環(huán)機(jī)制完成調(diào)度.

參考:

NodeJS文檔, https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/
JavaScript 運(yùn)行機(jī)制詳解:再談Event Loop, http://www.ruanyifeng.com/blog/2014/10/event-loop.html
樸靈 深入淺出Node.js http://www.infoq.com/cn/master-nodejs
8.結(jié)語

以上就是本人對事件循環(huán)的理解. 一家之言, 歡迎討論拍磚!
更多內(nèi)容, 可以關(guān)注, 微信公眾號, 小韓說理.

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/83187.html

相關(guān)文章

  • 深入淺出JavaScript運(yùn)行機(jī)制

    摘要:主線程從任務(wù)隊(duì)列中讀取事件,這個過程是循環(huán)不斷的,所以整個的這種運(yùn)行機(jī)制又稱為事件循環(huán)。上面也提到,在到達(dá)指定時間時,定時器就會將相應(yīng)回調(diào)函數(shù)插入任務(wù)隊(duì)列尾部。這就是定時器功能。關(guān)于定時器的重要補(bǔ)充定時器包括與兩個方法。 一、引子 本文介紹JavaScript運(yùn)行機(jī)制,這一部分比較抽象,我們先從一道面試題入手: console.log(1); setTimeout(function()...

    mochixuan 評論0 收藏0
  • 深入淺出JavaScript運(yùn)行機(jī)制

    摘要:主線程從任務(wù)隊(duì)列中讀取事件,這個過程是循環(huán)不斷的,所以整個的這種運(yùn)行機(jī)制又稱為事件循環(huán)。上面也提到,在到達(dá)指定時間時,定時器就會將相應(yīng)回調(diào)函數(shù)插入任務(wù)隊(duì)列尾部。這就是定時器功能。關(guān)于定時器的重要補(bǔ)充定時器包括與兩個方法。 一、引子 本文介紹JavaScript運(yùn)行機(jī)制,這一部分比較抽象,我們先從一道面試題入手: console.log(1); setTimeout(function()...

    魏明 評論0 收藏0
  • 深入淺出JavaScript運(yùn)行機(jī)制

    摘要:主線程從任務(wù)隊(duì)列中讀取事件,這個過程是循環(huán)不斷的,所以整個的這種運(yùn)行機(jī)制又稱為事件循環(huán)。上面也提到,在到達(dá)指定時間時,定時器就會將相應(yīng)回調(diào)函數(shù)插入任務(wù)隊(duì)列尾部。這就是定時器功能。關(guān)于定時器的重要補(bǔ)充定時器包括與兩個方法。 一、引子 本文介紹JavaScript運(yùn)行機(jī)制,這一部分比較抽象,我們先從一道面試題入手: console.log(1); setTimeout(function()...

    chaosx110 評論0 收藏0
  • JS異步詳解 - 瀏覽器/Node/循環(huán)/消息隊(duì)列/宏任務(wù)/微任務(wù)

    js異步歷史 一個 JavaScript 引擎會常駐于內(nèi)存中,它等待著我們把JavaScript 代碼或者函數(shù)傳遞給它執(zhí)行 在 ES3 和更早的版本中,JavaScript 本身還沒有異步執(zhí)行代碼的能力,引擎就把代碼直接順次執(zhí)行了,異步任務(wù)都是宿主環(huán)境(瀏覽器)發(fā)起的(setTimeout、AJAX等)。 在 ES5 之后,JavaScript 引入了 Promise,這樣,不需要瀏覽器的安排,J...

    awesome23 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<