国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

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

phpmatt / 1574人閱讀

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

在 Node.js 中,許許多多的異步操作,都需要來一個(gè)兜底的超時(shí),這時(shí),就輪到 timer 登場(chǎng)了。由于需要使用它的地方是那么的多,而且都是基礎(chǔ)的功能模塊,所以,對(duì)于它性能的要求,自然是十分高的。總結(jié)來說,要求有:

更快的添加操作。

更快的移除操作。

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

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

更快的添加 / 移除操作

說到添加和移除都十分高效的數(shù)據(jù)結(jié)構(gòu),第一個(gè)映入腦簾的,自然就是鏈表啦。是的,Node.js 就是使用了雙向鏈表,來將 timer 的插入和移除操作的時(shí)間復(fù)雜度都降至 O(1) 。雙向鏈表的具體實(shí)現(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;

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

更快的超時(shí)觸發(fā)

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

讓我們先以 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));
}

即檢查當(dāng)前在對(duì)象 map 中,是否存在該超時(shí)時(shí)間(msecs)的雙向鏈表,若無,則新建一條。你應(yīng)該已經(jīng)看出,超時(shí)觸發(fā)時(shí)具體的處理邏輯,就在 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();
  // ...
}

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

舉個(gè)圖例,在時(shí)間點(diǎn) 10,100,400 時(shí)分別注冊(cè)了三個(gè)超時(shí)時(shí)間為 1000 的 timer,在時(shí)間點(diǎn) 300 注冊(cè)了一個(gè)超時(shí)時(shí)間為 3000 的 timer,即在時(shí)間點(diǎn) 500 時(shí),對(duì)象 map 的結(jié)構(gòu)即為:

隨后在時(shí)間點(diǎn) 1200 觸發(fā)了超時(shí)事件,并在時(shí)間點(diǎn) 1300 執(zhí)行完畢,彼時(shí)對(duì)象 map 的結(jié)構(gòu)即為:

setInterval 和 setImmediate

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

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

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

  if (!timer._repeat)
    return;

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

setImmediatesetTimeout 實(shí)現(xiàn)上的主要區(qū)別則在于,它會(huì)一次性將鏈表中注冊(cè)的,都執(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),它們都會(huì)將存儲(chǔ)的回調(diào)都執(zhí)行完,但 process.nextTick 中的存儲(chǔ)的回調(diào),會(huì)先于 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

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/87765.html

相關(guān)文章

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

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

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

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

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

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

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

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

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

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

    atinosun 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<