这篇文章主要介绍“Libuv事件循环实现的逻辑是什么”,在日常操作中,相信很多人在Libuv事件循环实现的逻辑是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Libuv事件循环实现的逻辑是什么”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
Libuv是一个跨平台的的基于事件驱动的异步io库。但是他提供的功能不仅仅是io,包括进程、线程、信号、定时器、进程间通信等。
-
Libuv使用各平台提供的事件驱动模块实现异步(epoll, kqueue, IOCP, event
ports)。他用来支持上层非文件io的模块。libuv把上层的事件和回调封装成io观察者(uv__io_t)放到底层的事件驱动模块。当事件触发的时候,libuv会执行io观察者中的回调。 -
Libuv实现一个线程池用来支持上层文件io、dns以及用户层耗cpu的任务。
Libuv的整体执行架构
从上图中我们大致了解到,Libuv分为几个阶段,然后在一个循环里不断执行每个阶段里的任务。下面我们具体看一下每个阶段。
1 更新当前事件,在每次事件循环开始的时候,libuv会更新当前事件到变量中,这一轮循环的剩下操作可能使用这个变量获取当前事件,避免过多的系统调用影响性能。
2 如果时间循环是处于alive状态,则开始处理事件循环的每个阶段。否则退出这个事件循环。alive状态是什么意思呢?如果有active和ref状态的handle,active状态的request或者closing状态的handle则认为事件循环是alive的(具体实现后续会分析)。
3 timer阶段:判断最小堆中的节点哪个节点超时了,执行他的回调。
4 pending阶段:执行pending回调。一般来说,所有的io回调(网络,文件,dns)都会在poll io阶段执行。但是有的情况下,poll io阶段的回调会延迟到下一次循环执行,那么这种回调就是在pending阶段执行的。
5 idle阶段:如果节点处理avtive状态,每次事件循环都会被执行(idle不是说事件循环空闲的时候才执行)。
6 prepare阶段:和idle阶段一样。
如果时间循环是以UV_RUN_NOWAIT模式运行的,则timeout是0。
如果时间循环即将退出(调用了uv_stop),则timeout是0。
如果没有active状态的handle或者request,timeout是0。
如果有dile阶段的队列里有节点,则timeout是0。
如果有handle等待被关闭的(即调了uv_close),timeout是0。
如果上面的都不满足,则取timer阶段中最快超时的节点作为timeout,如果没有则timeout等于-1,即永远阻塞,直到满足条件。
8 poll io阶段:调用各平台提供的io多路复用接口,最多等待timeout时间。返回的时候,执行对应的回调。(比如linux下就是epoll模式)
9 check阶段:和idle prepare一样。
10 closing阶段:处理调用了uv_close函数的handle的回调。
11 如果libuv是以UV_RUN_ONCE模式运行的,那事件循环即将退出。但是有一种情况是,poll io阶段的timeout的值是timer阶段的节点的值。并且poll io阶段是因为超时返回的,即没有任何事件发生,也没有执行任何io回调。这时候需要在执行一次timer阶段。因为有节点超时了。
12 一轮事件循环结束,如果libuv以UV_RUN_NOWAIT 或 UV_RUN_ONCE模式运行的,则退出事件循环。如果是以UV_RUN_DEFAULT模式运行的并且状态是alive,则开始下一轮循环。否则退出事件循环。
下面是Libuv事件循环实现的逻辑。
int uv_run(uv_loop_t* loop, uv_run_mode mode) {
int timeout;
int r;
int ran_pending;
// 在uv_run之前要先提交任务到loop
r = uv__loop_alive(loop);
// 事件循环没有任务执行,即将退出,设置一下当前循环的时间
if (!r)
uv__update_time(loop);
// 没有任务需要处理或者调用了uv_stop
while (r != 0 && loop->stop_flag == 0) {
// 更新loop的time字段
uv__update_time(loop);
// 执行超时回调
uv__run_timers(loop);
// 执行pending回调,ran_pending代表pending队列是否为空,即没有节点可以执行
ran_pending = uv__run_pending(loop);
// 继续执行各种队列
uv__run_idle(loop);
uv__run_prepare(loop);
timeout = 0;
// 执行模式是UV_RUN_ONCE时,如果没有pending节点,才会阻塞式poll io,默认模式也是
if ((mode == UV_RUN_ONCE && !ran_pending) || mode == UV_RUN_DEFAULT)
timeout = uv_backend_timeout(loop);
// poll io timeout是epoll_wait的超时时间
uv__io_poll(loop, timeout);
uv__run_check(loop);
uv__run_closing_handles(loop);
// 还有一次执行超时回调的机会,因为poll io阶段可能是因为定时器超时返回的。
if (mode == UV_RUN_ONCE) {
uv__update_time(loop);
uv__run_timers(loop);
}
r = uv__loop_alive(loop);
// 只执行一次,退出循环,UV_RUN_NOWAIT表示在poll io阶段不会阻塞并且循环只执行一次
if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT)
break;
}
// 是因为调用了uv_stop退出的,重置flag
if (loop->stop_flag != 0)
loop->stop_flag = 0;
// 返回是否还有活跃的任务(handle或request),业务代表可以再次执行uv_run
return r;
}
到此,关于“Libuv事件循环实现的逻辑是什么”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!
原创文章,作者:kepupublish,如若转载,请注明出处:https://blog.ytso.com/223300.html