libuv学习笔记(5)
uv_timer_t 定时器的数据结构与相关API
所有的定时器存在一颗红黑树中,红黑树在tree.h中定义,通过宏的方式实现类似模板的功能。红黑树的具体实现可以参考sgi_stl的红黑树,差别不大,树节点的内存分配需要用户管理。
数据结构
在uv.h中定义
typedef struct uv_timer_s uv_timer_t;
struct uv_timer_s
{
UV_HANDLE_FIELDS//uv_handle_t的数据,此处不再展开,请参考之前的文章
//UV_TIMER_PRIVATE_FIELDS展开如下:
RB_ENTRY(uv_timer_s) tree_entry; //红黑树节点
uint64_t due;
uint64_t repeat; //重复执行的间隔,为零将只执行一次回调
uint64_t start_id;
uv_timer_cb timer_cb;//回调函数
};
相关函数
1.初始化函数,导出函数,在uv.h中声明,timer.c中定义
int uv_timer_init(uv_loop_t* loop, uv_timer_t* handle)
{
//uv__handle_init(loop, (uv_handle_t*) handle, UV_TIMER);宏展开如下:
do
{
(handle)->loop = (loop);
(handle)->type = (UV_TIMER);
(handle)->flags = UV__HANDLE_REF; /* 设置为引用状态 */
//将定时器handle添加到loop handle队列的末尾
QUEUE_INSERT_TAIL(&(loop)->handle_queue, &(handle)->handle_queue);
//uv__handle_platform_init(h); 宏展开如下:
((handle)->u.fd = -1)
}
while (0);
handle->timer_cb = NULL;
handle->repeat = 0;
return 0;
}
2.开启定时器,导出函数,在uv.h中声明,timer.c中定义
int uv_timer_start(uv_timer_t* handle,//定时器句柄
uv_timer_cb cb,//定时器回调函数
uint64_t timeout,//第一次回调间隔,毫秒
uint64_t repeat)//重复回调间隔,毫秒
{
uv_loop_t* loop = handle->loop;
uv_timer_t* old;
if (timer_cb == NULL)//必须设置回调函数
return UV_EINVAL;
//如果已经是激活状态,先停止。此时起到更新作用
//if (uv__is_active(handle))展开如下:
if ((((handle)->flags & UV__HANDLE_ACTIVE) != 0))
uv_timer_stop(handle);
handle->timer_cb = timer_cb;
//获取第一次执行相对loop的当前时间的时间。(loop->time + timeout)
handle->due = get_clamped_due_time(loop->time, timeout);
handle->repeat = repeat;
//uv__handle_start(handle);展开如下:
//激活定时器
do {
assert(((handle)->flags & UV__HANDLE_CLOSING) == 0);
if (((handle)->flags & UV__HANDLE_ACTIVE) != 0) break;
(handle)->flags |= UV__HANDLE_ACTIVE;
if (((handle)->flags & UV__HANDLE_REF) != 0)
//uv__active_handle_add(handle); 展开如下:
do
{
(h)->loop->active_handles++;//loop中活动的句柄数量加一
}
while (0)
}
while (0);
// start_id 是uv__timer_cmp()函数中的第二比较对象
handle->start_id = handle->loop->timer_counter++;//定时器数量加一,timer_counter只会递增
//将定时器插入红黑树(树中不应该存在相同的定时器——时间相同,strat_id也相同)
old = RB_INSERT(uv_timer_tree_s, &loop->timers, handle);
assert(old == NULL);
return 0;
}
3.停止定时器,导出函数,在uv.h中声明,timer.c中定义
int uv_timer_stop(uv_timer_t* handle)
{
//非线程安全
uv_loop_t* loop = handle->loop;
//非激活状态,直接返回
if (!uv__is_active(handle))
return 0;
//在红黑树中移除
RB_REMOVE(uv_timer_tree_s, &loop->timers, handle);
//uv__handle_stop(handle);展开:
do {
assert(((h)->flags & UV__HANDLE_CLOSING) == 0);
//非活动状态直接返回
if (((h)->flags & UV__HANDLE_ACTIVE) == 0) break;
(h)->flags &= ~UV__HANDLE_ACTIVE;//去掉激活状态标志
//如果定时器处于引用状态,loop活动的handle数减一
if (((h)->flags & UV__HANDLE_REF) != 0) uv__active_handle_rm(h);
}
while (0);
return 0;
}
4.再次运行,导出函数,在uv.h中声明,timer.c中定义
int uv_timer_again(uv_timer_t* handle)
{
/* 如果回调函数timer_cb 是null,意味着定时器还未开始 */
if (!handle->timer_cb)
{
return UV_EINVAL;
}
//如果重复运行间隔不为0,那么以间隔值重新开始定时器
if (handle->repeat) {
uv_timer_stop(handle);
uv_timer_start(handle, handle->timer_cb, handle->repeat, handle->repeat);
}
return 0;
}
5.设置重复间隔值,导出函数,在uv.h中声明,timer.c中定义
void uv_timer_set_repeat(uv_timer_t* handle, uint64_t repeat)
{
assert(handle->type == UV_TIMER);
handle->repeat = repeat;
}
6.定时器handle关闭时的处理(uv_close)
void uv_close(uv_handle_t* handle, uv_close_cb cb)
{
uv_loop_t* loop = handle->loop;
if (handle->flags & UV__HANDLE_CLOSING) {
assert(0);
return;
}
handle->close_cb = cb;
/* Handle-specific close actions */
switch (handle->type)
{
...
case UV_TIMER:
uv_timer_stop((uv_timer_t*)handle);//先停止定时器
//uv__handle_closing(handle);展开:
do {
assert(!((handle)->flags & UV__HANDLE_CLOSING));//不能处于正在关闭状态
if (!(((handle)->flags & UV__HANDLE_ACTIVE) &&
((handle)->flags & UV__HANDLE_REF)))
//非激活或者非引用状态下,先将loop的活动handle数量加一
//对于定时器,比如初始化但未开始的情况下,或者调用了uv_timer_stop之后。只有
//UV__HANDLE_REF,没有UV__HANDLE_ACTIVE
uv__active_handle_add((uv_handle_t*) (handle));
(handle)->flags |= UV__HANDLE_CLOSING;//正在关闭状态
(handle)->flags &= ~UV__HANDLE_ACTIVE;//非激活状态
} while (0)
//内部函数
uv_want_endgame(loop, handle);
return;
...
}
}
内部函数:
INLINE static void uv_want_endgame(uv_loop_t* loop, uv_handle_t* handle)
{
if (!(handle->flags & UV_HANDLE_ENDGAME_QUEUED))
{
//将本handle插入到loop需要关闭的handle列表的表头
handle->flags |= UV_HANDLE_ENDGAME_QUEUED;
handle->endgame_next = loop->endgame_handles;
loop->endgame_handles = handle;
}
}
最终,在uv_run中会调用内部函数处理所有需要关闭的句柄
INLINE static void uv_process_endgames(uv_loop_t* loop) {
uv_handle_t* handle;
while (loop->endgame_handles) {
handle = loop->endgame_handles;
loop->endgame_handles = handle->endgame_next;
handle->flags &= ~UV_HANDLE_ENDGAME_QUEUED;
switch (handle->type)
{
...
case UV_TIMER:
//处理定时器handle的关闭
uv_timer_endgame(loop, (uv_timer_t*) handle);
break;
...
}
}
}
void uv_timer_endgame(uv_loop_t* loop, uv_timer_t* handle)
{
if (handle->flags & UV__HANDLE_CLOSING)
{
assert(!(handle->flags & UV_HANDLE_CLOSED));
//uv__handle_close(handle);展开:
do {
//在handle队列中移除需要关闭的handle
QUEUE_REMOVE(&(handle)->handle_queue);
//活动handle计数递减
uv__active_handle_rm((uv_handle_t*) (handle));
//状态更新为已关闭
(handle)->flags |= UV_HANDLE_CLOSED;
//调用回掉函数
if ((handle)->close_cb)
(handle)->close_cb((uv_handle_t*) (handle));
} while (0)
}
}
整个定时器的关闭流程大致如下:
1.调用uv_close函数,针对uv_timer_t类型的handle,会先停止定时器
2.接着将loop中的活动handle计数加一,因为定时器handle如果调用过uv_timer_start,活动handle计数将会加一,而在调用了uv_timer_stop之后,会减一。此时无论是否调用过uv_timer_start,活动handle计数都没有再记录本handle,而接下来的操作仍然需要在loop中进行回调,所以需要此处在加一,之后的流程会减去。
3调用内部uv_want_endgame函数,讲本handle插入loop需要关闭的handle队列的头部。
4.uv_run中会在每个迭代中调用uv_process_endgames处理需要关闭的句柄。
5.在uv_process_endgames中,对于uv_timer_t会调用uv_timer_endgame
6.在uv_timer_endgame中,移除loop handle列表中的本handle,活动handle计数减一(对应第二步),跟新状态为UV__HANDLE_CLOSED,最后调用uv_close中传入的回调函数。
7.对于定时器的回调
在uv_run中会通过uv_process_timers处理所有的定时器
void uv_process_timers(uv_loop_t* loop)
{
uv_timer_t* timer;
/* 调用定时器回调 */
for (timer = RB_MIN(uv_timer_tree_s, &loop->timers);
timer != NULL && timer->due <= loop->time;//因为是红黑树,找到第一个触发时间比当前时间
//大的定时器之后就不用继续查找了
timer = RB_MIN(uv_timer_tree_s, &loop->timers)) {
uv_timer_stop(timer);
uv_timer_again(timer);
timer->timer_cb((uv_timer_t*) timer);
}
}
8.uv_timer_tree_s红黑树使用的比较函数
static int uv_timer_compare(uv_timer_t* a, uv_timer_t* b)
{
//线比较触发时间
if (a->due < b->due)
return -1;
if (a->due > b->due)
return 1;
//如果触发时间相同,比较start_id,start_id是在uv_timer_start函数中设置的当前loop的
//timer_counter(只会递增,所以同一loop中不会出现相同的值),
if (a->start_id < b->start_id)
return -1;
if (a->start_id > b->start_id)
return 1;
return 0;
}
libuv的定时器使用一颗全局的红黑树保存,以触发时间以及start_id作为key值比较,每次循环都会在更新了当前事件之后处理本loop所有的定时器,从出发时间最早的树节点(RB_MIN)开始。对于每一个定时器,处理流程如下:
1.先关闭,从红黑树删除
2.再以重复调用的间隔值重新开始,如果没有就意味着回调函数只调用一次,如果有重复间隔值,就以新的触发时间插入红黑树。
3.调用回调函数