EdwardZZZ / articles

工作点滴记录
2 stars 0 forks source link

Node.js event loop #37

Open EdwardZZZ opened 6 years ago

EdwardZZZ commented 6 years ago

https://github.com/nodejs/node/blob/master/deps/uv/src/unix/core.c#L357

// const fs = require('fs');

const output = (n = 1) => {
    console.log(`${n}\n\n`);
    setTimeout(() => {
        console.log(n, 'setTimeout');
    }, 0);
    setImmediate(() => {
        console.log(n, 'setImmediate');
        process.nextTick(() => {
            console.log(n, 'nextTick3');
        })
    });
    process.nextTick(() => {
        console.log(n, 'nextTick1');
    })
    process.nextTick(() => {
        console.log(n, 'nextTick2');
    })
};

setTimeout(() => { output(2) }, 1e1);
output();

setTimeout(() => {
    setTimeout(() => { output(4) }, 1e1);
    output(3);
}, 3e3);
EdwardZZZ commented 4 years ago
//deps/uv/src/unix/core.c
//deps/uv/src/unix/core.c
int uv_run(uv_loop_t *loop, uv_run_mode mode) {
    int timeout;
    int r;
    int ran_pending;
    //uv__loop_alive返回的是event loop中是否还有待处理的handle或者request
    //以及closing_handles是否为NULL,如果均没有,则返回0
    r = uv__loop_alive(loop);
    //更新当前event loop的时间戳,单位是ms
    if (!r)
        uv__update_time(loop);
    while (r != 0 && loop->stop_flag == 0) {
        //使用Linux下的高精度Timer hrtime更新loop->time,即event loop的时间戳
        uv__update_time(loop);
        //执行判断当前loop->time下有无到期的Timer,显然在同一个loop里面timer拥有最高的优先级
        uv__run_timers(loop);
        //判断当前的pending_queue是否有事件待处理,并且一次将&loop->pending_queue中的uv__io_t对应的cb全部拿出来执行
        ran_pending = uv__run_pending(loop);
        //实现在loop-watcher.c文件中,一次将&loop->idle_handles中的idle_cd全部执行完毕(如果存在的话)
        uv__run_idle(loop);
        //实现在loop-watcher.c文件中,一次将&loop->prepare_handles中的prepare_cb全部执行完毕(如果存在的话)
        uv__run_prepare(loop);

        timeout = 0;
        //如果是UV_RUN_ONCE的模式,并且pending_queue队列为空,或者采用UV_RUN_DEFAULT(在一个loop中处理所有事件),则将timeout参数置为
        //最近的一个定时器的超时时间,防止在uv_io_poll中阻塞住无法进入超时的timer中
        if ((mode == UV_RUN_ONCE && !ran_pending) || mode == UV_RUN_DEFAULT)
            timeout = uv_backend_timeout(loop);
        //进入I/O处理的函数(重点分析的部分),此处挂载timeout是为了防止在uv_io_poll中陷入阻塞无法执行timers;并且对于mode为
        //UV_RUN_NOWAIT类型的uv_run执行,timeout为0可以保证其立即跳出uv__io_poll,达到了非阻塞调用的效果
        uv__io_poll(loop, timeout);
        //实现在loop-watcher.c文件中,一次将&loop->check_handles中的check_cb全部执行完毕(如果存在的话)
        uv__run_check(loop);
        //执行结束时的资源释放,loop->closing_handles指针指向NULL
        uv__run_closing_handles(loop);

        if (mode == UV_RUN_ONCE) {
            //如果是UV_RUN_ONCE模式,继续更新当前event loop的时间戳
            uv__update_time(loop);
            //执行timers,判断是否有已经到期的timer
            uv__run_timers(loop);
        }
        r = uv__loop_alive(loop);
        //在UV_RUN_ONCE和UV_RUN_NOWAIT模式中,跳出当前的循环
        if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT)
            break;
    }

    //标记当前的stop_flag为0,表示当前的loop执行完毕
    if (loop->stop_flag != 0)
        loop->stop_flag = 0;
    //返回r的值
    return r;
}
EdwardZZZ commented 4 years ago

在node中,setTimeout(cb, 0) === setTimeout(cb, 1); setImmediately属于uv_run_check的部分 每次loop进来,都是先检查uv_run_timer的,但是由于cpu工作耗费时间,比如第一次获取的hrtime为0 那么setTimeout(cb, 1),超时时间就是loop->time = 1(ms,node定时器精确到1ms,但是hrtime是精确到纳秒级别的) 所以第一次loop进来的时候就有两种情况:

1.由于第一次loop前的准备耗时超过1ms,当前的loop->time >=1 ,则uv_run_timer生效,timeout先执行
2.由于第一次loop前的准备耗时小于1ms,当前的loop->time = 0,则本次loop中的第一次uv_run_timer不生效,那么io_poll后先执行uv_run_check,即immediate先执行,然后等close cb执行完后,继续执行uv_run_timer

为什么在回调中,一定是先immediate执行呢,其实也很容易理解

由于timeout和immediate的事件注册是在readFile的回调执行时触发的 所以必然的,在readFile的回调执行前的每一次event loop进来的uv_run_timer都不会有超时事件触发 那么当readFile执行完毕,kevent收到监听的fd事件完成后,执行了该回调,此时

1.timeout事件注册
2.immediate事件注册
3.由于readFile的回调执行完毕,那么就会从uv_io_poll中出来,此时立即执行uv_run_check,所以immediate事件被执行掉
4.最后的uv_run_timer检查timeout事件,执行timeout事件

所以在I/O回调中注册的两者,永远都是immediately先执行

EdwardZZZ commented 4 years ago
const hello = (str = 'hello') => {
    process.nextTick(() => console.log(`before ${str}`));
    console.log(str);
    process.nextTick(() => console.log(`after ${str}`));
}

const world = (str = 'world') => {
    process.nextTick(() => console.log(`before ${str}`));
    console.log(str);
    process.nextTick(() => console.log(`after ${str}`));
}

const foo = (str = 'foo') => {
    process.nextTick(() => console.log(`before ${str}`));
    console.log(str);
    process.nextTick(() => console.log(`after ${str}`));
}

const bar = (str = 'bar') => {
    process.nextTick(() => console.log(`before ${str}`));
    console.log(str);
    process.nextTick(() => console.log(`after ${str}`));
}

// const hello = (str = 'hello') => {
//     Promise.resolve().then(() => console.log(`before ${str}`));
//     console.log(str);
//     Promise.resolve().then(() => console.log(`after ${str}`));
// }

// const world = (str = 'world') => {
//     Promise.resolve().then(() => console.log(`before ${str}`));
//     console.log(str);
//     Promise.resolve().then(() => console.log(`after ${str}`));
// }

// const foo = (str = 'foo') => {
//     Promise.resolve().then(() => console.log(`before ${str}`));
//     console.log(str);
//     Promise.resolve().then(() => console.log(`after ${str}`));
// }

// const bar = (str = 'bar') => {
//     Promise.resolve().then(() => console.log(`before ${str}`));
//     console.log(str);
//     Promise.resolve().then(() => console.log(`after ${str}`));
// }

(async () => {
    await hello();
    await world();
    await foo();
    await bar();
})()