偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

Nodejs深度探秘:Event Loop的本質(zhì)和異步代碼中的Zalgo問題

開發(fā) 前端
在主線程的循環(huán)中,它會不斷輪詢特定隊(duì)列,看看是否有數(shù)據(jù)可以處理,如果有那么它就從隊(duì)列中取下來,然后將數(shù)據(jù)進(jìn)行處理后發(fā)送給需要的客戶端。

Nodejs是一個(gè)高效的異步服務(wù)平臺,因此非常適合于開發(fā)高并發(fā)的后臺服務(wù)。要滿足高并發(fā),后臺服務(wù)需要做到的是能夠及時(shí)響應(yīng)客戶端發(fā)送過來的請求。這里要注意的是”響應(yīng)“而不是”完成“,客戶端可能要求后臺從數(shù)據(jù)庫查詢特定數(shù)據(jù),后臺接收請求后會告訴客戶端”你的要求我收到而且正在處理,當(dāng)我處理完成了再通知你”。由此NodeJS能完成高并發(fā)的原因在于,它會將那些耗時(shí)長的處理提交給線程池處理,它的主線程則一直響應(yīng)客戶端的請求,等到線程池把耗時(shí)久的任務(wù)完成,主線程拿到結(jié)果后再發(fā)送給對應(yīng)的客戶。

因此NodeJS的基本模式是,由一個(gè)主線程不斷接收客戶端請求,如果請求需要一定時(shí)間才完成,主線程會將任務(wù)丟給線程池,然后繼續(xù)回頭處理其他客戶的請求。在主線程的循環(huán)中,它會不斷輪詢特定隊(duì)列,看看是否有數(shù)據(jù)可以處理,如果有那么它就從隊(duì)列中取下來,然后將數(shù)據(jù)進(jìn)行處理后發(fā)送給需要的客戶端。由于主線程不用長時(shí)間阻塞,因此它能夠在給定時(shí)間內(nèi)對大量的客戶端請求進(jìn)行響應(yīng),這是它能實(shí)現(xiàn)高并發(fā)的原因。

主線程不斷輪詢特定隊(duì)列是否有數(shù)據(jù)的過程也叫event loop。其基本流程如下:

NodeJS代碼的特點(diǎn)在于,任何我們自己寫的代碼,它在執(zhí)行時(shí)一定在主線程中,而且你不用擔(dān)心因多線程導(dǎo)致的重入等問題。在NodeJS代碼中,一旦有異步調(diào)用產(chǎn)生,執(zhí)行流就會將這個(gè)調(diào)用提交給它的線程池,然后直接指向異步調(diào)用后面的代碼,例如:

console.log(1)
setTimer(()=>{console.log(2), 0)
console.log(3)

上面代碼運(yùn)行時(shí)輸出結(jié)果是1,3,2,這是因?yàn)閟etTimer是異步函數(shù),在主線程里不會得到執(zhí)行,主線程會把這個(gè)時(shí)鐘任務(wù)交給線程池,等到時(shí)鐘結(jié)束后,里面的回調(diào)就會放置在上圖中的時(shí)鐘隊(duì)列,因此主線程會越過setTimer直接指向它后面的語句,等到主線程下次循環(huán)到上圖中的時(shí)鐘隊(duì)列位置時(shí)才會把setTimer設(shè)置的回調(diào)函數(shù)拿出來執(zhí)行。

由此對于NodeJS的event loop來說它包含若干個(gè)階段,每個(gè)階段對應(yīng)上圖的一個(gè)方塊。在每個(gè)階段,主線程會從對應(yīng)隊(duì)列中獲取數(shù)據(jù)返回給客戶端,或者是將存儲在隊(duì)列中的回調(diào)函數(shù)進(jìn)行執(zhí)行,當(dāng)隊(duì)列清空,或者訪問的隊(duì)列元素超過給定值后就會進(jìn)入下一個(gè)階段。

從上圖可以看出,所有時(shí)鐘相關(guān)的回調(diào)都在Timer階段執(zhí)行,例如代碼使用setTimer, setInterval等接口時(shí),NodeJS會把時(shí)鐘請求提交給操作系統(tǒng),一旦時(shí)鐘結(jié)束后,操作系統(tǒng)會通知NodeJS,后者就會把時(shí)鐘對應(yīng)的回調(diào)掛入Timer階段對應(yīng)的隊(duì)列。第二個(gè)階段是操作系統(tǒng)在某項(xiàng)情況下需要通知特定事件給NodeJS,例如TCP連接請求被拒絕,數(shù)據(jù)庫連接失敗等;idle階段屬于nodejs內(nèi)部使用,主線程會執(zhí)行一些nodejs內(nèi)部特定回調(diào)函數(shù)執(zhí)行一些內(nèi)部事務(wù),這部分通常與我們開發(fā)無關(guān);poll階段應(yīng)該是nodejs主線程的主要工作所在,當(dāng)文件打開成功,數(shù)據(jù)從文件中讀入,或者數(shù)據(jù)寫入文件等相應(yīng)IO事件發(fā)生時(shí),對應(yīng)的回調(diào)函數(shù)都會存儲在這個(gè)階段的隊(duì)列,典型的fs.writeFile(p, (err, data)=>{})調(diào)用,它對應(yīng)的回調(diào)函數(shù)就在這個(gè)階段才能執(zhí)行。check階段執(zhí)行由setImmediate提交的回調(diào)函數(shù),setImmediate和setTimeout(callback, 0)其實(shí)性質(zhì)一樣,只不過這兩個(gè)異步函數(shù)對應(yīng)的回調(diào)在不同的階段執(zhí)行,如果我們再代碼中同時(shí)執(zhí)行setImmediate和setTimeout(callback, 0),那么哪個(gè)回調(diào)先執(zhí)行就取決于主線程當(dāng)前處于哪個(gè)階段,我們可以做個(gè)實(shí)驗(yàn),在本地創(chuàng)建一個(gè)文件例如hello.txt,然后創(chuàng)建index.js,在里面添加代碼如下:

setTimeout(function() {
console.log('setTimeout')
}, 0)

setImmediate(function() {
console.log('setImmediate')
})

在多次運(yùn)行index.js情況下,有時(shí)候setTimeout先打印,有時(shí)候setImmediate先打印,這取決于主線程處于哪個(gè)階段,如果它執(zhí)行時(shí)主線程已經(jīng)越過check階段,那么setTimeout將先打印,反之亦然。如果我們在IO回調(diào)中執(zhí)行上面代碼,例如:

fs.readFile('./hello.txt', ()=> {
setTimeout(function() {
console.log('setTimeout in read file')
}, 0)

setImmediate(function() {
console.log('setImmediate in read file')
})
})

那么setImmediate in read file一定會先打印,因?yàn)閞eadFile的回調(diào)在poll階段執(zhí)行,而check階段緊跟著poll,因此讀取文件的回調(diào)執(zhí)行后主線程進(jìn)入check階段,于是setImmediate設(shè)置的回調(diào)一定先執(zhí)行。

上圖中還有一個(gè)process.nextTick,它也是一個(gè)異步函數(shù),但它不屬于event loop的任何階段,當(dāng)當(dāng)前event loop階段走完重新回到timer階段時(shí),主線程會先查看是否有nextTick提供的回調(diào),如果有,那么先執(zhí)行給定回調(diào)然后再進(jìn)入timer階段。它本質(zhì)上跟setImmediate沒有什么區(qū)別,只不過后者屬于event loop的特定階段而前者不屬于event loop,因此它最大的作用是讓代碼在主線程進(jìn)入下一輪循環(huán)前做一些操作,例如釋放掉一些沒用的資源。

由于nodejs的異步模式,有些錯(cuò)誤可能很難處理,這類問題稱之為Zalgo問題,他們的特點(diǎn)是把同步邏輯和異步邏輯組合在一起從而導(dǎo)致難以復(fù)現(xiàn)和難以調(diào)試的Bug,一個(gè)例子如下:

import {readFile} from 'fs'

const cache = new Map()

function problemRead(filename, cb) {
if (cache.has(filename)) {
cb(cache.get(filename))
} else {
readFile(filename, 'utf8', (err, data)= {
cache.set(filename, data)
cb(data)
})
}
}

在上面代碼中,problemRead有兩種模式,一種是如果緩存沒有存在,那么使用readFile進(jìn)行異步讀取,如果緩存已經(jīng)存在,那么cb對應(yīng)的回調(diào)函數(shù)將直接執(zhí)行,因此cb有可能在執(zhí)行時(shí)存在不同上下文環(huán)境,這種情況很容易導(dǎo)致代碼出現(xiàn)問題,例如創(chuàng)建文件zalgo.mjs,實(shí)現(xiàn)代碼如下:

function createFileReader(filename) {
const listeners = []
problemRead(filename, value=>{
listeners.forEach(listener => listener(value))
})

return {
onDataReady: listener => listeners.push(listener)
}
}

const reader1 = createFileReader('./hello.txt')
reader1.onDataReady(data => {
console.log("calling from reader1: ", data)

const reader2 = createFileReader('./hello.txt')
reader2.onDataReady(data => {
//這里的回調(diào)不會被調(diào)用
console.log('calling from reader2: ', data)
})
})

上面代碼執(zhí)行時(shí)只會輸出:

calling from reader1:  hello world!

也就是read2對應(yīng)的回調(diào)沒有調(diào)用。它的原因是這樣,第一次調(diào)用createFileReader時(shí),由于數(shù)據(jù)沒有緩存,因此代碼調(diào)用異步接口readFile,前面我們說過任何異步調(diào)用都會提交內(nèi)線程池,它絕不會在主線程中運(yùn)行,因此readFile接下來的代碼會直接運(yùn)行,于是我們就有機(jī)會把reader1對應(yīng)的回調(diào)加入到listeners隊(duì)列,等到回調(diào)完成后,reader1的回調(diào)函數(shù)已經(jīng)存儲在listeners中,于是在回調(diào)中遍歷listeners隊(duì)列,取出其中的回調(diào)函數(shù)執(zhí)行,這樣reader1指定的回調(diào)就能得以執(zhí)行。

在reader2對應(yīng)的createFileReader函數(shù)執(zhí)行后,對應(yīng)的數(shù)據(jù)已經(jīng)存儲在緩存中,于是代碼直接將listener2隊(duì)列中的回調(diào)元素拿出來執(zhí)行,注意這個(gè)時(shí)候reader2.onDataReady對應(yīng)代碼還沒有執(zhí)行,因此reader2對應(yīng)的回調(diào)函數(shù)還沒有來得及放入到listeners隊(duì)列,于是它就得不到執(zhí)行的機(jī)會。這種問題很難調(diào)試,首先它不好重現(xiàn),如果createReader后面繼續(xù)存在被調(diào)用,那么reader2對應(yīng)的回調(diào)就可以被執(zhí)行,同時(shí)上面代碼reader2的回調(diào)沒有執(zhí)行,同時(shí)代碼也不產(chǎn)生任何異?;蝈e(cuò)誤,這使得問題的定位會非常困難,nodejs社區(qū)把這種問題叫做upleasing zalgo,這是一個(gè)特定的典故。這給我們的教訓(xùn)是,在代碼中要不全部使用異步模式,要不就同步模式,決不能兩種交叉混合使用。

責(zé)任編輯:武曉燕 來源: Coding迪斯尼
相關(guān)推薦

2022-06-29 08:37:03

事件循環(huán)JS 語言

2013-10-24 15:23:40

Event Loop

2013-08-01 11:47:03

ERPSAP

2009-03-11 10:29:23

代碼契約.NETCLR

2023-04-28 15:20:37

JavaScript事件循環(huán)

2022-08-29 18:15:25

Node.js多線程模型

2017-03-06 16:13:41

深度學(xué)習(xí)人工智能

2025-05-20 07:13:22

Spring異步解耦Event

2019-10-11 09:00:00

JavaScriptEvent Loop前端

2017-09-12 09:50:08

JavaScriptEvent LoopVue.js

2021-01-11 07:52:30

系統(tǒng)cpu工具

2021-04-27 08:31:06

event loopJavaScriptsetTimeout函

2020-09-28 14:41:24

Event Loop

2017-09-14 13:48:20

Vue.js機(jī)制應(yīng)用

2009-11-23 09:34:05

WPF本質(zhì)

2024-02-05 21:07:51

C++內(nèi)存編程語言

2010-03-29 16:48:18

Nginx內(nèi)核優(yōu)化

2012-11-22 09:14:34

華為存儲閃存

2022-10-17 13:35:23

EventJS工具

2021-02-06 23:21:35

SaaS開發(fā)低代碼
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號