nodejs 14.0.0源碼分析之setImmediate

  • 2020 年 2 月 25 日
  • 筆記

我們從setImmediate開始分析,

function setImmediate(callback, arg1, arg2, arg3) {    if (typeof callback !== 'function') {      throw new ERR_INVALID_CALLBACK(callback);    }      let i, args;    switch (arguments.length) {      case 1:        break;      case 2:        args = [arg1];        break;      case 3:        args = [arg1, arg2];        break;      default:        args = [arg1, arg2, arg3];        for (i = 4; i < arguments.length; i++) {          args[i - 1] = arguments[i];        }        break;    }      return new Immediate(callback, args);  }  

setImmediate的代碼比較簡單,新建一個Immediate。我們看一下Immediate的類。

const Immediate = class Immediate {    constructor(callback, args) {      this._idleNext = null;      this._idlePrev = null;      this._onImmediate = callback;      this._argv = args;      this._destroyed = false;      this[kRefed] = false;        initAsyncResource(this, 'Immediate');        this.ref();      // Immediate鏈表的節點個數,包括ref和unref狀態      immediateInfo[kCount]++;      // 加入鏈表中      immediateQueue.append(this);    }    // 打上ref標記,往libuv的idle鏈表插入一個節點,如果還沒有的話    ref() {      if (this[kRefed] === false) {        this[kRefed] = true;        if (immediateInfo[kRefCount]++ === 0)          toggleImmediateRef(true);      }      return this;    }    // 和上面相反    unref() {      if (this[kRefed] === true) {        this[kRefed] = false;        if (--immediateInfo[kRefCount] === 0)          toggleImmediateRef(false);      }      return this;    }      hasRef() {      return !!this[kRefed];    }  };  

Immediate類主要做了兩個事情。 1 生成一個節點插入到鏈表。

const immediateQueue = new ImmediateList();  
// 雙向非循環的鏈表  function ImmediateList() {    this.head = null;    this.tail = null;  }    // Appends an item to the end of the linked list, adjusting the current tail's  // previous and next pointers where applicable  ImmediateList.prototype.append = function(item) {    // 尾指針非空,說明鏈表非空,直接追加在尾節點後面    if (this.tail !== null) {      this.tail._idleNext = item;      item._idlePrev = this.tail;    } else {      // 尾指針是空說明鏈表是空的,頭尾指針都指向item      this.head = item;    }    this.tail = item;  };    // Removes an item from the linked list, adjusting the pointers of adjacent  // items and the linked list's head or tail pointers as necessary  ImmediateList.prototype.remove = function(item) {    // 如果item在中間則自己全身而退,前後兩個節點連上    if (item._idleNext !== null) {      item._idleNext._idlePrev = item._idlePrev;    }      if (item._idlePrev !== null) {      item._idlePrev._idleNext = item._idleNext;    }    // 是頭指針,則需要更新頭指針指向item的下一個,因為item被刪除了,尾指針同理    if (item === this.head)      this.head = item._idleNext;    if (item === this.tail)      this.tail = item._idlePrev;    // 重置前後指針    item._idleNext = null;    item._idlePrev = null;  };  

2 然後如果還沒有往libuv的idle鏈表裡插入節點的話,則插入一個。

void ToggleImmediateRef(const FunctionCallbackInfo<Value>& args) {    Environment::GetCurrent(args)->ToggleImmediateRef(args[0]->IsTrue());  }    void Environment::ToggleImmediateRef(bool ref) {    if (started_cleanup_) return;    // 往idle鏈表插入/刪除一個節點,插入節點是防止在poll io階段阻塞    if (ref) {      // Idle handle is needed only to stop the event loop from blocking in poll.      uv_idle_start(immediate_idle_handle(), [](uv_idle_t*){ });    } else {      uv_idle_stop(immediate_idle_handle());    }  }  

這是setImmediate函數的整個過程,他是一個生產者。我們來看一下消費者。nodejs在初始化的時候,會在check階段插入一個節點,並註冊一個回調。

  uv_check_start(immediate_check_handle(), CheckImmediate);  
void Environment::CheckImmediate(uv_check_t* handle) {      // 省略部分代碼    // 沒有Immediate節點需要處理    if (env->immediate_info()->count() == 0 || !env->can_call_into_js())      return;      do {        // 執行js層回調immediate_callback_function      MakeCallback(env->isolate(),                   env->process_object(),                   env->immediate_callback_function(),                   0,                   nullptr,                   {0, 0}).ToLocalChecked();    } while (env->immediate_info()->has_outstanding() && env->can_call_into_js());    // 所有的immediate節點都處理完了,刪除idle鏈表的那個節點,即允許poll io階段阻塞    if (env->immediate_info()->ref_count() == 0)      env->ToggleImmediateRef(false);  }  

CheckImmediate函數會在libuv的check階段被執行。然後他執行immediate_callback_function函數處理immediate鏈表的節點。我們看一下immediate_callback_function函數是在哪設置的。

const { setupTimers } = internalBinding('timers')  setupTimers(processImmediate, processTimers);  // js層setupTimers =》 c++層 SetupTimers  void SetupTimers(const FunctionCallbackInfo<Value>& args) {    CHECK(args[0]->IsFunction());    CHECK(args[1]->IsFunction());    auto env = Environment::GetCurrent(args);      env->set_immediate_callback_function(args[0].As<Function>());    env->set_timers_callback_function(args[1].As<Function>());  }  

所以processImmediate函數就是真正處理immediate鏈表的函數。

const immediateQueue = new ImmediateList();  const outstandingQueue = new ImmediateList()    function processImmediate() {      // 上次執行processImmediate的時候有遺留的節點則執行outstandingQueue隊列,這時候immediateQueue隊列是空的      const queue = outstandingQueue.head !== null ?        outstandingQueue : immediateQueue;      let immediate = queue.head;        /*        在執行immediateQueue隊列的話,先置空隊列,避免執行回調的時候一直往隊列加節點,死循環。        所以新加的接口會插入新的隊列,不會在本次被執行。        並打一個標記,全部immediateQueue節點都被執行則清空,否則會再執行processImmediate一次,見Environment::CheckImmediate      */      if (queue !== outstandingQueue) {        queue.head = queue.tail = null;        immediateInfo[kHasOutstanding] = 1;      }        let prevImmediate;      let ranAtLeastOneImmediate = false;      while (immediate !== null) {        // 執行宏任務        if (ranAtLeastOneImmediate)          runNextTicks();        else          ranAtLeastOneImmediate = true;          // 宏任務把該節點刪除了,則不需要指向他的回調了,繼續下一個        if (immediate._destroyed) {          outstandingQueue.head = immediate = prevImmediate._idleNext;          continue;        }          immediate._destroyed = true;        // 執行完要修改個數        immediateInfo[kCount]--;        if (immediate[kRefed])          immediateInfo[kRefCount]--;        immediate[kRefed] = null;        // 見上面if (immediate._destroyed)的注釋        prevImmediate = immediate;          // 執行回調,指向下一個節點        try {          const argv = immediate._argv;          if (!argv)            immediate._onImmediate();          else            immediate._onImmediate(...argv);        } finally {          immediate._onImmediate = null;            if (destroyHooksExist())            emitDestroy(asyncId);            outstandingQueue.head = immediate = immediate._idleNext;        }      }      // 當前執行的是outstandingQueue的話則把他清空      if (queue === outstandingQueue)        outstandingQueue.head = null;      // 全部節點執行完      immediateInfo[kHasOutstanding] = 0;    }  

這就是setImmediate的原理。