非I/O的异步API

在介绍Node的时候,大多提到异步I/O,但是Node中其实还存在一些与I/O无关的异步API,分别是setTimeout()setInterval()setImmediate()process.nextTick()

定时器

setTimeout()setInterval()与浏览器中的API是一致的,分别用于单次和多次定时执行任务。
它们的实现原理与异步I/O比较类似,只是不需要I/O线程池的参与。
调用setTimeout()或者setInterval()创建的定时器会被插入到 定时器观察者 内部的一个 红黑树 中。每次Tick执行时,会从该 红黑树 中迭代取出定时器对象,检查是否超过定时时间,如果超过,就形成一个事件,它的回调函数将立即执行。
如下图主要是setTimeout()的行为。setInterval()与之相同,区别在于后者是重复性的检测和执行。

定时器的问题在于,它并非精确的(在容忍范围内)。
尽管事件循环十分快,但是如果某一次循环占用的时间较多,那么下次循环时,它也许已经超时很久了。譬如通过setTimeout()设定一个任务在10毫秒后执行,但是在9毫秒后,有一个任务占用了5毫秒的CPU时间片,再次轮到定时器执行时,时间就已经过期4毫秒。

process.nextTick()

很多人为了立即异步执行一个任务,会这样调用setTimeout()来达到所需的效果:

setTimeout(function () {
  // TODO
}, 0);

由于事件循环自身的特点,定时器的精确度不够。
另外,采用定时器需要动用红黑树,创建定时器对象和迭代等操作,因此setTimeout(fn, 0)的方式较为浪费性能。实际上,process.nextTick()方法的操作相对较为轻量,具体代码如下:

process.nextTick = function (callback) {
    // on the way out, don't bother.
    // it won't get fired anyway 
    if (process._exiting) return;
    if (tickDepth >= process.maxTickDepth)
        maxTickWarn();
    var tock = { callback: callback };
    if (process.domain) tock.domain = process.domain;
    nextTickQueue.push(tock);
    if (nextTickQueue.length) {
        process._needTickCallback();
    }
}

每次调用process.nextTick()方法,只会将回调函数放入队列中,在下一轮Tick时取出执行。定时器中采用红黑树的操作时间复杂度为O(lg(n))nextTick()的时间复杂度为O(1)。相较之下,process.nextTick()更高效

setImmediate()

setImmediate()方法与process.nextTick()方法十分类似,都是将回调函数延迟执行。在Nodev0.9.1之前,setImmediate()
还没有实现,那时候实现类似的功能主要是通过process.nextTick()来完成,代码如下所示:

process.nextTick(function () {
    console.log('延迟执行');
});
console.log('正常执行');

输出结果如下:

正常执行
延迟执行

setImmediate()实现时,相关代码如下:

setImmediate(function () {
    console.log('延迟执行');
});
console.log('正常执行');

输出结果如下:

正常执行
延迟执行

两者输出结果完全一样,但是两者之间还是有细微差别的。下面把他们放在一起,看看谁的优先级更高:

process.nextTick(function () {
    console.log('nextTick延迟执行');
});
setImmediate(function () {
    console.log('setImmediate延迟执行');
});
console.log('正常执行')

执行结果如下:

正常执行
nextTick延迟执行
setImmediate延迟执行

从结果里可以看到,process.nextTick()中的回调函数执行的优先级要高于setImmediate()
这里的原因在于事件循环对 观察者的检查 是有先后顺序的。
process.nextTick()属于idle观察者,setImmediate()属于check观察者。
在每一个轮循环检查中,idle观察者 先于 I/O观察者,I/O观察者 先于 check观察者。

在具体实现上,process.nextTick()的回调函数保存在一个 数组 中,setImmediate()的结果则是保存在 链表 中。
在行为上,process.nextTick()在每轮循环中会将数组中的回调函数全部执行完,而setImmediate()在每轮循环中执行链表中的一个回调函数。
如下的示例代码可以佐证:

process.nextTick(function () {
    console.log('nextTick执行1');
});
process.nextTick(function () {
    console.log('nextTick执行2');
});

setImmediate(function () {
    console.log('setImmediate执行1');
    // 进入下次循环
    process.nextTick(function () {
        console.log('强势插入');
    });
});
setImmediate(function () {
    console.log('setImmediate执行2');
});

console.log('正常执行');

执行结果如下:

正常执行
nextTick执行1
nextTick执行2
setImmediate执行1
强势插入
setImmediate执行2

从执行结果上可以看出,当第一个setImmediate()的回调函数执行后,并没有立即执行第二个,而是进入了下一轮循环,再次按process.nextTick()优先、setImmediate()次后的顺序执行。
之所以这样设计,是为了保证每轮循环能够较快地执行结束,防止CPU占用过多而阻塞后续I/O调用的情况。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值