libuv的实现是一个很经典生产者-消费者模型。libuv在整个生命周期中,每一次循环都执行每个阶段(phase)维护的任务队列。逐个执行节点里的回调,在回调中,不断生产新的任务,从而不断驱动libuv。今天我们分析一下libuv的整体架构,从而学会如何使用libuv。我们从libuv的一个小例子开始。
代码语言:javascript复制#include <stdio.h>
#include <uv.h>
int64_t counter = 0;
void wait_for_a_while(uv_idle_t* handle) {
counter ;
if (counter >= 10e6)
uv_idle_stop(handle);
}
int main() {
uv_idle_t idler;
// 获取事件循环的核心结构体。并初始化一个idler
uv_idle_init(uv_default_loop(), &idler);
// 往事件循环的idle节点插入一个任务
uv_idle_start(&idler, wait_for_a_while);
// 启动事件循环
uv_run(uv_default_loop(), UV_RUN_DEFAULT);
// 销毁libuv的相关数据
uv_loop_close(uv_default_loop());
return 0;
}
使用libuv,我们首先需要获取libuv的核心结构体uv_loop_t。uv_loop_t是一个非常大的结构体。里面记录了libuv整个生命周期的数据。
uv_default_loop为我们提供了一个默认已经初始化了的uv_loop_t结构体。当然我们也可以自己去分配一个,自己初始化。
代码语言:javascript复制uv_loop_t* uv_default_loop(void) {
// 缓存
if (default_loop_ptr != NULL)
return default_loop_ptr;
if (uv_loop_init(&default_loop_struct))
return NULL;
default_loop_ptr = &default_loop_struct;
return default_loop_ptr;
}
libuv维护了一个全局的uv_loop_t结构体,使用uv_loop_init进行初始化。不打算展开uv_loop_init函数。因为他大概就是对uv_loop_t结构体各个字段进行初始化。接着我们看一下uv_idle_*系列的函数。 1 uv_idle_init
代码语言:javascript复制 int uv_idle_init(uv_loop_t* loop, uv_idle_t* handle) {
/*
初始化handle的类型,所属loop,打上UV_HANDLE_REF,
并且把handle插入loop->handle_queue队列的队尾
*/
uv__handle_init(loop, (uv_handle_t*)handle, UV_IDLE);
handle->idle_cb = NULL;
return 0;
}
执行uv_idle_init函数后,libuv的内存视图如下
2 uv_idle_start
代码语言:javascript复制 int uv_idle_start(uv_idle_t* handle, uv_idle_cb cb) {
// 如果已经执行过start函数则直接返回
if (uv__is_active(handle)) return 0;
if (cb == NULL) return UV_EINVAL;
// 把handle插入loop中idle的队列
QUEUE_INSERT_HEAD(&handle->loop->idle_handles, &handle->queue);
// 挂载回调,下一轮循环的时候被执行
handle->idle_cb = cb;
/*
设置UV_HANDLE_ACTIVE标记位,并且loop中的handle数加一,
init的时候只是把handle挂载到loop,start的时候handle才处于激活态
*/
uv__handle_start(handle);
return 0;
}
执行完uv_idle_start的内存视图
然后执行uv_run进入libuv的事件循环。
代码语言:javascript复制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;
}
/* The if statement lets gcc compile it to a conditional store. Avoids
* dirtying a cache line.
*/
// 是因为调用了uv_stop退出的,重置flag
if (loop->stop_flag != 0)
loop->stop_flag = 0;
// 返回是否还有活跃的任务(handle或request),业务代表可以再次执行uv_run
return r;
}
我们看到有一个函数是uv__run_idle。这就是处理idle阶段的函数。我们看一下他的实现。
代码语言:javascript复制 // 在每一轮循环中执行该函数,执行时机见uv_run
void uv__run_idle(uv_loop_t* loop) {
uv_idle_t* h;
QUEUE queue;
QUEUE* q;
// 把该类型对应的队列中所有节点摘下来挂载到queue变量,变量回调里不断插入新节点,导致死循环
QUEUE_MOVE(&loop->idle_handles, &queue);
// 遍历队列,执行每个节点里面的函数
while (!QUEUE_EMPTY(&queue)) {
// 取下当前待处理的节点
q = QUEUE_HEAD(&queue);
// 取得该节点对应的整个结构体的基地址
h = QUEUE_DATA(q, uv_idle_t, queue);
// 把该节点移出当前队列,否则循环不会结束
QUEUE_REMOVE(q);
// 重新插入原来的队列
QUEUE_INSERT_TAIL(&loop->idle_handles, q);
// 执行回调函数
h->idle_cb(h);
}
}
我们看到uv__run_idle的逻辑并不复杂。就是遍历idle_handles队列的节点,然后执行回调。在回调里我们可以插入新的节点(产生新任务)。从而不断驱动libuv的运行。我们看到uv_run退出循环的条件下面的代码为false
代码语言:javascript复制r != 0 && loop->stop_flag == 0
stop_flag由用户主动关闭libuv事件循环。
代码语言:javascript复制void uv_stop(uv_loop_t* loop) {
loop->stop_flag = 1;
}
r是代表事件循环是否还存活,这个判断的标准是由uv__loop_alive提供
代码语言:javascript复制static int uv__loop_alive(const uv_loop_t* loop) {
return loop->active_handles > 0 ||
loop->active_reqs.count > 0 ||
loop->closing_handles != NULL;
}
这时候我们有一个actived handles。所以libuv不会退出。当我们调用uv_idle_stop函数把idle节点移出handle队列的时候,libuv就会退出。
代码语言:javascript复制int uv_idle_stop(uv_idle_t* handle) {
if (!uv__is_active(handle)) return 0;
// 把idle节点从loop中idle队列移除,但是还挂载到handle_queue中
QUEUE_REMOVE(&handle->queue);
// 清除active标记位并且减去loop中handle的active数
uv__handle_stop(handle);
return 0;
}
执行uv_idle_stop后active_handles为0,这时候事件循环就结束了。