亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > JavaScript > 正文

Node.js Stream ondata觸發時機與順序的探索

2019-11-19 12:01:03
字體:
來源:轉載
供稿:網友

上次寫Stream pipe細節時,在源碼中發現一段無用邏輯,由此引發了對Stream data事件觸發時機與順序的探索。

無用邏輯

當時研究pipe細節是基于Node.js v8.11.1的源碼,其中針對上游的ondata事件處理有如下一段代碼:

// If the user pushes more data while we're writing to dest then we'll end up// in ondata again. However, we only want to increase awaitDrain once because// dest will only emit one 'drain' event for the multiple writes.// => Introduce a guard on increasing awaitDrain.var increasedAwaitDrain = false;src.on('data', ondata);function ondata(chunk) {  debug('ondata');  increasedAwaitDrain = false;  var ret = dest.write(chunk);  if (false === ret && !increasedAwaitDrain) {    if (((state.pipesCount === 1 && state.pipes === dest) ||        (state.pipesCount > 1 && state.pipes.indexOf(dest) !== -1)) &&      !cleanedUp) {      debug('false write response, pause', src._readableState.awaitDrain);      src._readableState.awaitDrain++;      increasedAwaitDrain = true;    }    src.pause();  }}

重點關注increasedAwaitDrain變量,理解這個變量期望達到什么目的,然后仔細閱讀代碼,會發現if (false === ret && !increasedAwaitDrain)語句中increasedAwaitDrain變量肯定是false,因為前一行才將該變量賦值為false,這樣一來這個變量就變得毫無意義。

increasedAwaitDrain = false; var ret = dest.write(chunk); if (false === ret && !increasedAwaitDrain) {}

以上就是關鍵的三行代碼,因為Node.js是單線程且dest.write(chunk)內部沒有修改變量increasedAwaitDrain的值,那么if語句中increasedAwaitDrain的值肯定還是false,即increasedAwaitDrain相關邏輯沒有達到所期望的目標。

無用代碼出現的原因

前段雖已經分析出increasedAwaitDrain沒起到作用,但作者為什么寫了這樣一段邏輯呢?其實在定義increasedAwaitDrain語句的上方,作者說可能存在這樣一種情況:“當我們接收到一次上游的ondata事件并嘗試將數據寫到下游時,上游可能同時又有一個data事件觸發,而這兩個ondata的數據在寫入下游時可能都返回false,從而導致src._readableState.awaitDrain++執行兩次”。

awaitDrain++執行兩次是作者不希望看到的情況,因為下游觸發drain事件時awaitDrain相應減1,直到其值為0時才讓上游重新流動,如果awaitDrain++執行兩次,下游卻只觸發一次drain事件,awaitDrain就不會為0,上游不重新流動也就無法繼續讀取數據。

真相的探索過程

雖然從理性上認為increasedAwaitDrain沒起到作用,但也無法肯定加絕對,自己嘗試去求助,沒有出現高手指點出問題所在,但一個同事聽我描述后,說可能這就是個BUG,雖心中覺得可能性不大,但還是抱著試試看的心態切換到master分支上去瞅瞅,隨即發現最新的代碼里并沒有與increasedAwaitDrain類似的邏輯,間接說明v8.11.1分支上increasedAwaitDrain相關邏輯的確無用。

雖然比較肯定這里存在一段無用代碼,但應該如何理解作者在increasedAwaitDrain上方的注釋呢?為了進一步揭露真相,自己繼續花時間去看了看stream.Readable相關代碼,想知道data事件的觸發時機與順序是如何決定的。

readable流的簡單原理

在進一步解釋data事件的觸發順序前,簡單講一下readable流的實現原理,如果需要自己實現一個readable流,可以使用new stream.Readable(options)方法,其中options可包含四個屬性:highWaterMark、encoding、objectMode、read。最主要的是read屬性,當流的使用者需要數據時,read方法被用來從數據源獲取數據,然后通過this.push(chunk)將數據傳遞給使用者,如果沒有更多數據可供讀取時使用this.push(null)表示讀取結束。

const Readable = require('stream').Readable;let letter = 'ABCDEFG'.split('');let index = 0;const rs = new Readable({  read(size) {    this.push(letter[index++] || null);  }});rs.on('data', chunk => {  console.log(chunk.toString());});// 輸出// A// B// C// ...

這里ondata雖然沒有明顯調用read方法,但內部依舊是通過調用read方法結合this.push輸出數據,并且在源代碼內部可以發現通過參數傳遞的read方法實際上被賦值給this._read,然后在Readable.prototype.read中調用this._read獲取數據。

靈魂代碼

為了進一步說明stream.Readable的data事件觸發順序與場景,將有關官方源碼經過修改和刪減成如下:

function Readable(options) {  this._read = options.read; // 將參數傳遞的read函數賦值到this._read}// 使用者通過調用read方法獲取數據Readable.prototype.read = function (size) {  var state = this._readableState;  // 模擬鎖,一次_read如果沒有返回(this.push),后續read不會繼續調用_read讀取數據  if (!state.reading) {    state.reading = true;    state.sync = true; // sync用于在push方法中指示_read內部是否同步調用了push    this._read(size);    state.sync = false;      }  // _read內部如果是同步調用push,數據會放入緩沖區  // _read內部如果是異步調用push且緩沖區沒有內容,數據可能emit data返回  // 嘗試從緩沖區(state.buffer)中獲取大小為size的數據,如果獲取成功則觸發data事件  if (ret)     this.emit('data', ret);  return ret;};// 在this._read執行過程中通過this.push輸出數據Readable.prototype.push = function (chunk, encoding) {  var state = this._readableState;  // 本次_read獲取到數據,打開鎖  state.reading = false;  // 流動模式 & 緩沖區沒有數據 & 非同步返回,則直接觸發data事件  if (state.flowing && state.length === 0 && !state.sync) {    stream.emit('data', chunk);    stream.read(0); // 觸發下一次讀取,_read異步push的話還是會到這里,類似flow中的保持流出于流動  }  else {    // 將數據放入緩沖區    state.length += chunk.length;    state.buffer.push(chunk);  }};// 暫停流動Readable.prototype.pause = function() {  if (this._readableState.flowing !== false) {    this._readableState.flowing = false;    this.emit('pause');  }  return this;};function flow(stream) {  const state = stream._readableState;  while (state.flowing && stream.read() !== null);}

data事件的觸發時機與順序

時機

data的觸發只有兩處:

  • 流如果處于流動模式 & 緩沖區沒有數據 & 異步調用push,此時數據不經過緩沖區,直接觸發data事件
  • 不滿足上述情況時,push的數據會被放入緩沖區,然后再嘗試從緩沖區讀取指定size的數據并觸發data事件

順序

關于data的觸發順序,實際是由emit順序決定,為討論原始問題:“increasedAwaitDrain相關邏輯為什么可以被刪除?”,將代碼簡化:

let count = 0;src.on('data', chunk => {  let ret = dest.write(chunk);  if (!ret) {    count++;    src.pause();  }});

當監聽流的data事件時,流最終會通過resume并調用flow函數進入流動模式模式,即不斷的調用read方法讀取數據。接下來分析以下幾種場景,當dest.write(chunk)返回false時++count會執行幾次,注意結合前文的靈魂代碼。

  • 場景一:每次_read同步push一次數據

當發生第一次讀取,數據同步push到緩沖區,緊接著從緩沖區中讀取數據并通過emit data的方式傳遞到ondata中,如果此時dest.write(chunk)返回false,count++將執行一次,接著由于調用了stream.pause(),while條件state.flowing為false導致stream.read不再被調用,在流重新流動前,count的值不會繼續增加。

  • 場景二:每次_read異步push一次數據

當發生第一次讀取,異步push的數據將直接通過emit data傳遞到ondata中,而read函數中的emit由于無法從緩沖區讀取數據從而不會觸發,同時read返回null導致while循環也相應停止,此種情況下異步push觸發data事件后,緊接著的stream.read(0)會繼續保持流的流動,當dest.write(chunk)返回false,count++執行一次并將流暫停,緊接著會繼續調用一次read,但這次數據將被放入緩沖區且不觸發data事件,count++依舊只執行一次。

場景二流暫停一次后再次流動時,數據消耗模式與之前會有所差異,會優先消耗緩沖區數據直至為空時回到之前的模式,但這同樣不會導致count++執行多次。

  • 場景三:每次_read多次同步push數據

與場景一類似,只是每次_read會多次往緩沖區寫入數據,最終data事件還是依靠從緩沖區讀數據后觸發。

  • 場景四:每次_read多次異步push數據

同場景二類似,假設在一次_read中有兩次異步push,當第一個異步push執行時,data事件觸發且其中的dest.write(chunk)返回false,導致count++同時流被暫停,等第二個異步push執行時,由于流已經暫停,數據將寫入緩沖區而不是觸發data事件,所以count++只執行一次。

  • 場景五:_read操作可能同步或異步push

不管是同步或者異步push,當一次ondata內部將流設置為暫停模式后,flow函數中while條件state.flowing為false將導致stream.read不再調用,異步的push的emit data判斷條件同樣不再滿足,即目前階段內部不會再有data事件觸發直到外部再次間接或直接調用read方法。

以上五個場景是為了分析該問題而模擬的,實際只要能理解第五個場景就能明白所有。

小結

文章最終寫出來的內容與我最開始的初衷所偏離,而且自己不知道如何評價這篇文章的好壞,但為了寫這文章花了兩天業余時間去深入理解stream.Readable卻是非常有收獲的一件事情,更堅定自己在寫文章的路途上可以走的更遠。

PS:猜測為什么有爛電影的存在,可能是因為導演長時間投入的創作會讓他迷失在內部而無法發現問題,寫文章也是,難以通過閱讀去優化費心思寫的文章。

PS:下圖是美團博客的,也許我寫了這么多卻抵不上這張圖,說明方式很重要。

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對武林網的支持。如果你想了解更多相關內容請查看下面相關鏈接

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
日本精品免费观看| 国模精品视频一区二区| 欧美精品videosex性欧美| 国产一区二区美女视频| 在线免费观看羞羞视频一区二区| 亚洲一区亚洲二区亚洲三区| 久久理论片午夜琪琪电影网| 国产不卡一区二区在线播放| 国产精品入口免费视| 亚洲va欧美va国产综合剧情| 欧美激情视频播放| 国产精品9999| 成人免费xxxxx在线观看| 一区国产精品视频| 国模精品视频一区二区| 中文在线资源观看视频网站免费不卡| 日韩精品在线观看网站| 麻豆一区二区在线观看| 91九色国产在线| 日韩极品精品视频免费观看| 久久精品视频导航| 亚洲精品国产免费| 欧美丰满老妇厨房牲生活| 国外成人性视频| 亚洲色图第一页| 亚洲精品免费网站| 午夜免费久久久久| 91久久在线播放| 日韩a**中文字幕| 国产在线拍揄自揄视频不卡99| 国产成人精品在线| 日韩成人高清在线| 久久久在线视频| 日韩中文字幕视频| 亚洲乱码一区二区| 亚洲最大福利网站| 黑人巨大精品欧美一区二区一视频| 精品国产老师黑色丝袜高跟鞋| 神马国产精品影院av| 国产精品一区=区| 2019中文字幕在线观看| 国产成人福利网站| 国产视频亚洲视频| 青青草一区二区| 狠狠综合久久av一区二区小说| 国产91在线播放九色快色| 欧美精品久久久久久久久久| 一区二区三区亚洲| 欧美怡春院一区二区三区| 亚洲综合日韩中文字幕v在线| 亚洲xxxx视频| 成人福利网站在线观看| 成人xvideos免费视频| 久久国产精品久久久| 亚洲第一色中文字幕| 国产精品一区二区久久精品| 91超碰中文字幕久久精品| 亚洲无亚洲人成网站77777| 日韩在线观看免费高清| 成人激情视频免费在线| 亚洲精品电影网站| 久久影视电视剧免费网站| 久久免费视频在线观看| 亚洲第一福利网| 欧美日韩色婷婷| 精品一区二区三区三区| 亚洲第一福利视频| 久久久久久久久91| 欧美二区乱c黑人| 日韩中文第一页| 欧美高清自拍一区| 国产精品电影久久久久电影网| 精品无人国产偷自产在线| 国内精品久久久久久中文字幕| 亚洲亚裔videos黑人hd| 日韩在线视频网站| 亚洲伊人一本大道中文字幕| 久久精品视频99| 韩国日本不卡在线| 国产免费亚洲高清| 在线视频欧美日韩精品| 在线一区二区日韩| 亚洲区中文字幕| 久久精品欧美视频| 亚洲影视中文字幕| 亚洲国产精彩中文乱码av在线播放| 中文字幕精品视频| 日韩美女在线观看| 国产成人精品在线观看| 亚洲美女精品久久| 成人精品网站在线观看| 孩xxxx性bbbb欧美| 欧美国产在线视频| 亚洲精品女av网站| 成人欧美一区二区三区在线湿哒哒| 亚洲天堂av在线免费观看| 国产精品女人久久久久久| 欧美成人精品一区二区| 亚洲精品国偷自产在线99热| 日韩中文在线中文网在线观看| 国产精品久久久久久亚洲影视| 国产精品高清在线观看| 欧美黄色片在线观看| 狠狠色噜噜狠狠狠狠97| 永久免费精品影视网站| 乱亲女秽乱长久久久| 欧美性猛交xxxx乱大交3| 国产精品视频不卡| 久久伊人91精品综合网站| 欧美成人免费一级人片100| 欧美亚州一区二区三区| 亚洲天堂第一页| 日韩在线视频网| 欧美黄网免费在线观看| 欧美一区二粉嫩精品国产一线天| 26uuu国产精品视频| 国产v综合ⅴ日韩v欧美大片| 成人久久精品视频| 黑人精品xxx一区一二区| 成人福利网站在线观看| 欧美精品福利视频| 国色天香2019中文字幕在线观看| 久久精品成人欧美大片| 国产精品高清免费在线观看| 夜色77av精品影院| 亚洲aaa激情| 久久综合九色九九| 亚洲成人1234| 操91在线视频| 欧美激情免费在线| 97国产精品免费视频| 国产欧美在线播放| 九色精品美女在线| 日韩有码视频在线| 欧美大片网站在线观看| 亚洲女性裸体视频| 国产精品自产拍高潮在线观看| 91在线无精精品一区二区| 欧美在线视频免费| 久久影视电视剧免费网站清宫辞电视| 日韩中文字幕国产精品| 国产69精品久久久久9999| 在线亚洲欧美视频| 伊人久久五月天| 亚洲国模精品一区| 色与欲影视天天看综合网| 欧美中文字幕在线观看| 亚洲人成77777在线观看网| 在线一区二区日韩| 亚洲2020天天堂在线观看| 欧美xxxx18国产| 久久久久久久久电影| 国产v综合v亚洲欧美久久| 国产这里只有精品| 欧美成人精品不卡视频在线观看| 青青草原一区二区| 国产美女直播视频一区| 久久电影一区二区| 91爱爱小视频k| 国产午夜精品全部视频在线播放| 欧美精品久久一区二区| 欧美精品videos另类日本| 亚洲色图校园春色| 黑人巨大精品欧美一区二区|