Open huenchao opened 4 years ago
其实event是由uv_run驱动的,并且是在UV_RUN_ONCE的模式下执行 UV_RUN_ONCE就是你文中描述的
poll 阶段: 获取新的I/O事件, 适当的条件下node将阻塞在这里; 中的“适当条件”,它会阻塞在kqueue的kevent()函数中(这里以mac为例,linux下这一块由epoll_wait()函数处理,windows下则是IOCP) 这里描述UV_RUN_ONCE为适当条件,是因为uv_run还有两种模式,对于UV_RUN_NOWAIT的模式,io_poll不会阻塞,会立即触发超时结束当前的event_loop,进入下一次循环(v4.4.2版本中仅用在进程退出前的最后检查) 实际上,你描述的timer,在一次event loop中可能在两个地方执行:
最开始以及close callbacks结束之后 为什么这么说呢,就算不看源代码,我们也可以思考这样的一个场景: 当你设置了一个超时timer,那么第一次进入uv_run_timer时,超时时间未到;此时进入kevent()阻塞等待 如果此处不提供超时机制,那就会永远阻塞在kevent()的I/O等待中;所以我们在 当前的event loop循环中的某一次时,如果存在timer且为达到设定时间,则会把最近的一个timer剩余超时时间作为参数传入io_poll()中,这样kevent()等待时,如果没有任何I/O事件触发,也会由timerout触发跳出等待的操作,结束本次时间循环 所以呢,在UV_RUN_ONCE的模式下,每次循环结束前,即你说的close callback执行结束后,会再执行一次对timer的超时判断 具体涉及到的代码如下:
//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; }
地址:https://cnodejs.org/topic/57d68794cb6f605d360105bf
其实event是由uv_run驱动的,并且是在UV_RUN_ONCE的模式下执行 UV_RUN_ONCE就是你文中描述的
poll 阶段: 获取新的I/O事件, 适当的条件下node将阻塞在这里; 中的“适当条件”,它会阻塞在kqueue的kevent()函数中(这里以mac为例,linux下这一块由epoll_wait()函数处理,windows下则是IOCP) 这里描述UV_RUN_ONCE为适当条件,是因为uv_run还有两种模式,对于UV_RUN_NOWAIT的模式,io_poll不会阻塞,会立即触发超时结束当前的event_loop,进入下一次循环(v4.4.2版本中仅用在进程退出前的最后检查) 实际上,你描述的timer,在一次event loop中可能在两个地方执行:
最开始以及close callbacks结束之后 为什么这么说呢,就算不看源代码,我们也可以思考这样的一个场景: 当你设置了一个超时timer,那么第一次进入uv_run_timer时,超时时间未到;此时进入kevent()阻塞等待 如果此处不提供超时机制,那就会永远阻塞在kevent()的I/O等待中;所以我们在 当前的event loop循环中的某一次时,如果存在timer且为达到设定时间,则会把最近的一个timer剩余超时时间作为参数传入io_poll()中,这样kevent()等待时,如果没有任何I/O事件触发,也会由timerout触发跳出等待的操作,结束本次时间循环 所以呢,在UV_RUN_ONCE的模式下,每次循环结束前,即你说的close callback执行结束后,会再执行一次对timer的超时判断 具体涉及到的代码如下: