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的原理。