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

資訊專欄INFORMATION COLUMN

通過源碼解析 Node.js 中高效的 timer

phpmatt / 1602人閱讀

摘要:雙向鏈表的具體實現(xiàn)便在中可以看到,都是些修改鏈表中指針的操作,都十分高效。如此一來,既做到了定時器的復用優(yōu)化,又對鏈表結構進行了揚長避短。

在 Node.js 中,許許多多的異步操作,都需要來一個兜底的超時,這時,就輪到 timer 登場了。由于需要使用它的地方是那么的多,而且都是基礎的功能模塊,所以,對于它性能的要求,自然是十分高的??偨Y來說,要求有:

更快的添加操作。

更快的移除操作。

更快的超時觸發(fā)。

接下來就讓我們跟著 Node.js 項目中的 lib/timer.jslib/internal/linklist.js 來探究它具體的實現(xiàn)。

更快的添加 / 移除操作

說到添加和移除都十分高效的數(shù)據(jù)結構,第一個映入腦簾的,自然就是鏈表啦。是的,Node.js 就是使用了雙向鏈表,來將 timer 的插入和移除操作的時間復雜度都降至 O(1) 。雙向鏈表的具體實現(xiàn)便在 lib/internal/linklist.js 中:

// lib/internal/linklist.js
"use strict";
function init(list) {
  list._idleNext = list;
  list._idlePrev = list;
}
exports.init = init;

function peek(list) {
  if (list._idlePrev == list) return null;
  return list._idlePrev;
}
exports.peek = peek;

function shift(list) {
  var first = list._idlePrev;
  remove(first);
  return first;
}
exports.shift = shift;

function remove(item) {
  if (item._idleNext) {
    item._idleNext._idlePrev = item._idlePrev;
  }

  if (item._idlePrev) {
    item._idlePrev._idleNext = item._idleNext;
  }

  item._idleNext = null;
  item._idlePrev = null;
}
exports.remove = remove;

function append(list, item) {
  remove(item);
  item._idleNext = list._idleNext;
  list._idleNext._idlePrev = item;
  item._idlePrev = list;
  list._idleNext = item;
}
exports.append = append;

function isEmpty(list) {
  return list._idleNext === list;
}
exports.isEmpty = isEmpty;

可以看到,都是些修改鏈表中指針的操作,都十分高效。

更快的超時觸發(fā)

鏈表的缺點,自然是它的查找時間,對于一個無序的鏈表來說,查找時間需要 O(n) ,但是,只要基于一個大前提,那么我們的實現(xiàn)就并不需要使用到鏈表的查詢,這也是更高效的超時觸發(fā)的基礎所在,那就是,對于同一延遲的 timers ,后添加的一定比先添加的晚觸發(fā)。所以,源碼的具體做法就是,對于同一延遲的所有 timers ,全部都維護在同一個雙向鏈表中,后來的,就不斷往鏈表末尾追加,并且這條鏈表實際上共享同一個定時器 。這個定時器會在當次超時觸發(fā)時,動態(tài)計算下一次的觸發(fā)時間點。所有的鏈表,都保存在一個對象 map 中。如此一來,既做到了定時器的復用優(yōu)化,又對鏈表結構進行了揚長避短。

讓我們先以 setTimeout 為例看看具體代碼,首先是插入:

// lib/timer.js
// ...
const refedLists = {};
const unrefedLists = {};

exports.setTimeout = function(callback, after) {
  // ... 
  var timer = new Timeout(after);
  var length = arguments.length;
  var ontimeout = callback;
  // ...
  timer._onTimeout = ontimeout;
  
  active(timer);
  return timer;
};

const active = exports.active = function(item) {
  insert(item, false);
};

function insert(item, unrefed) {
  const msecs = item._idleTimeout;
  if (msecs < 0 || msecs === undefined) return;

  item._idleStart = TimerWrap.now();

  var list = lists[msecs];
  if (!list) {
    // ...
    list = new TimersList(msecs, unrefed);
    L.init(list);
    list._timer._list = list;

    if (unrefed === true) list._timer.unref();
    list._timer.start(msecs, 0);

    lists[msecs] = list;
    list._timer[kOnTimeout] = listOnTimeout;
  }

  L.append(list, item);
  assert(!L.isEmpty(list));
}

即檢查當前在對象 map 中,是否存在該超時時間(msecs)的雙向鏈表,若無,則新建一條。你應該已經(jīng)看出,超時觸發(fā)時具體的處理邏輯,就在 listOnTimeout 函數(shù)中:

// lib/timer.js
// ...
function listOnTimeout() {
  var list = this._list;
  var msecs = list.msecs;

  var now = TimerWrap.now();

  var diff, timer;
  while (timer = L.peek(list)) {
    diff = now - timer._idleStart;

    if (diff < msecs) {
      this.start(msecs - diff, 0);
      return;
    }
    L.remove(timer);
    // ...
    tryOnTimeout(timer, list);
    // ...
  }

  this.close();
  // ...
}

即不斷從鏈表頭取出封裝好的包含了注冊時間點處理函數(shù)的對象,然后挨個執(zhí)行,直到計算出的超時時間點已經(jīng)超過當前時間點。

舉個圖例,在時間點 10,100,400 時分別注冊了三個超時時間為 1000 的 timer,在時間點 300 注冊了一個超時時間為 3000 的 timer,即在時間點 500 時,對象 map 的結構即為:

隨后在時間點 1200 觸發(fā)了超時事件,并在時間點 1300 執(zhí)行完畢,彼時對象 map 的結構即為:

setInterval 和 setImmediate

setInterval 的實現(xiàn)總體和 setTimeout 很相似,區(qū)別在于對注冊的回調(diào)函數(shù)進行了封裝,在鏈表的尾部重新插入:

// lib/timer.js
// ...

function wrapper() {
  timer._repeat(); // 執(zhí)行傳入的回調(diào)函數(shù)

  if (!timer._repeat)
    return;

  // ...
  timer._idleTimeout = repeat;
  active(timer);
}

setImmediatesetTimeout 實現(xiàn)上的主要區(qū)別則在于,它會一次性將鏈表中注冊的,都執(zhí)行完:

// lib/timer.js
// ...
function processImmediate() {
  var queue = immediateQueue;
  var domain, immediate;

  immediateQueue = {};
  L.init(immediateQueue);

  while (L.isEmpty(queue) === false) {
    immediate = L.shift(queue);
    // ...
    tryOnImmediate(immediate, queue);
    // ...
  }

  if (L.isEmpty(immediateQueue)) {
    process._needImmediateCallback = false;
  }
}

所以作為功能類似的 process.nextTicksetImmediate ,在功能層面上看,每次事件循環(huán),它們都會將存儲的回調(diào)都執(zhí)行完,但 process.nextTick 中的存儲的回調(diào),會先于 setImmediate 中的執(zhí)行:

"use strict"
const print = (i) => () => console.log(i)

process.nextTick(print(1))
process.nextTick(print(2))

setImmediate(() => {
  print(3)()
  setImmediate(print(6))
  process.nextTick(print(5))
})
setImmediate(print(4))

console.log("發(fā)車")

// 發(fā)車
// 1
// 2
// 3
// 4
// 5
// 6
最后

參考:

https://github.com/nodejs/node/blob/master/lib/timers.js

https://github.com/nodejs/node/blob/master/lib/internal/linkedlist.js

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

轉載請注明本文地址:http://systransis.cn/yun/87765.html

相關文章

  • setTimeout與setInterval區(qū)別和nodejs差異

    摘要:關于定時器的源碼在文件中,進入就關于定時器的一些設計解釋,因為是做服務端代碼,在內(nèi)部等大部分事件都會創(chuàng)建一個定時器,任何時間都可能存在大量的定時器任務,所以設計一個高效的定時器是很有必要的。 博客文章地址 setTimeout與setInterval setTimeout 和 setInterval 是我們在 javaScript 中經(jīng)常用到的定時器,setTimeout 方法用于...

    meislzhua 評論0 收藏0
  • 通過源碼解析 Node.js 啟動時第一個執(zhí)行 js 文件:bootstrap_node.js

    摘要:注很多以前的源碼分析文章中,所寫的第一個執(zhí)行的文件代碼為,但這個文件在中已被移除,并被拆解為了等其他下的文件,為正文作為第一段被執(zhí)行的代碼,它的歷史使命免不了就是進行一些環(huán)境和全局變量的初始化工作。 大家可能會好奇,在 Node.js 啟動后,第一個執(zhí)行的 JavaScript 文件會是哪個?它具體又會干些什么事? 一步步來看,翻開 Node.js 的源碼,不難看出,入口文件在 src...

    TNFE 評論0 收藏0
  • 瀏覽器與Node事件循環(huán)(Event Loop)有何區(qū)別?

    摘要:事件觸發(fā)線程主要負責將準備好的事件交給引擎線程執(zhí)行。它將不同的任務分配給不同的線程,形成一個事件循環(huán),以異步的方式將任務的執(zhí)行結果返回給引擎。 Fundebug經(jīng)作者浪里行舟授權首發(fā),未經(jīng)同意請勿轉載。 前言 本文我們將會介紹 JS 實現(xiàn)異步的原理,并且了解了在瀏覽器和 Node 中 Event Loop 其實是不相同的。 一、線程與進程 1. 概念 我們經(jīng)常說 JS 是單線程執(zhí)行的,...

    TANKING 評論0 收藏0
  • 做面試不倒翁:一道事件循環(huán)題引發(fā)血案

    摘要:通過查看的文檔可以發(fā)現(xiàn)整個分為個階段定時器相關任務,中我們關注的是它會執(zhí)行和中到期的回調(diào)執(zhí)行某些系統(tǒng)操作的回調(diào)內(nèi)部使用執(zhí)行,一定條件下會在這個階段阻塞住執(zhí)行的回調(diào)如果或者關閉了,就會在這個階段觸發(fā)事件,執(zhí)行事件的回調(diào)的代碼在文件中。 showImg(https://segmentfault.com/img/bVbd7B7?w=1227&h=644); 這次我們就不要那么多前戲,直奔主題...

    ispring 評論0 收藏0
  • Node事件循環(huán)和異步API

    摘要:異步在中,是在單線程中執(zhí)行的沒錯,但是內(nèi)部完成工作的另有線程池,使用一個主進程和多個線程來模擬異步。在事件循環(huán)中,觀察者會不斷的找到線程池中已經(jīng)完成的請求對象,從中取出回調(diào)函數(shù)和數(shù)據(jù)并執(zhí)行。 1. 介紹 單線程編程會因阻塞I/O導致硬件資源得不到更優(yōu)的使用。多線程編程也因為編程中的死鎖、狀態(tài)同步等問題讓開發(fā)人員頭痛。Node在兩者之間給出了它的解決方案:利用單線程,遠離多線程死鎖、狀態(tài)...

    atinosun 評論0 收藏0

發(fā)表評論

0條評論

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