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