libuv的定时器原理源码解析
首先我们看一下timer的结构体
struct uv_timer_s {
void* data;
uv_loop_t* loop;
uv_handle_type type;
uv_close_cb close_cb;
void* handle_queue[2];
union {
int fd;
void* reserved[4];
} u;
uv_handle_t* next_closing;
unsigned int flags;
uv_timer_cb timer_cb;
void* heap_node[3];
uint64_t timeout;
uint64_t repeat;
uint64_t start_id;
}
如果需要使用定时器,首先要对定时器的结构体进行初始化。
#if defined(_WIN32)
# define uv__handle_platform_init(h) ((h)->u.fd = -1)
#else
# define uv__handle_platform_init(h) ((h)->next_closing = NULL)
#endif
#define uv__handle_init(loop_, h, type_) \
do { \
(h)->loop = (loop_); \
(h)->type = (type_); \
(h)->flags = UV_HANDLE_REF; /* Ref the loop when active. */ \
QUEUE_INSERT_TAIL(&(loop_)->handle_queue, &(h)->handle_queue); \
uv__handle_platform_init(h); \
} \
while (0)
// 初始化uv_timer_t结构体
int uv_timer_init(uv_loop_t* loop, uv_timer_t* handle) {
uv__handle_init(loop, (uv_handle_t*)handle, UV_TIMER);
handle->timer_cb = NULL;
handle->repeat = 0;
return 0;
}
接着我们需要启动一个定时器。
// 启动一个计时器
int uv_timer_start(uv_timer_t* handle,
uv_timer_cb cb,
uint64_t timeout,
uint64_t repeat) {
uint64_t clamped_timeout;
if (cb == NULL)
return UV_EINVAL;
// 重新执行start的时候先把之前的停掉
if (uv__is_active(handle))
uv_timer_stop(handle);
// 超时时间,为绝对值
clamped_timeout = handle->loop->time + timeout;
if (clamped_timeout < timeout)
clamped_timeout = (uint64_t) -1;
// 初始化回调,超时时间,是否重复计时,赋予一个独立无二的id
handle->timer_cb = cb;
handle->timeout = clamped_timeout;
handle->repeat = repeat;
/* start_id is the second index to be compared in uv__timer_cmp() */
handle->start_id = handle->loop->timer_counter++;
// 插入最小堆
heap_insert(timer_heap(handle->loop),
(struct heap_node*) &handle->heap_node,
timer_less_than);
// 激活该handle
uv__handle_start(handle);
return 0;
}
这时候的内存视图是。 在uv_run的时候会执行过期的定时器。
// 找出已经超时的节点,并且执行里面的回调
void uv__run_timers(uv_loop_t* loop) {
struct heap_node* heap_node;
uv_timer_t* handle;
for (;;) {
heap_node = heap_min(timer_heap(loop));
if (heap_node == NULL)
break;
handle = container_of(heap_node, uv_timer_t, heap_node);
// 如果当前节点的时间大于当前时间则返回,说明后面的节点也没有超时
if (handle->timeout > loop->time)
break;
// 移除该计时器节点,重新插入最小堆,如果设置了repeat的话
uv_timer_stop(handle);
uv_timer_again(handle);
// 执行超时回调
handle->timer_cb(handle);
}
}
执行定时器的时候首先会先移除该定时器,然后如果设置了repeat的话,再次加入到最小堆里,最后执行超时回调。这里有个需要注意的是设置了repeat的定时器,意思是timeout时间后触发第一次超时,后面每隔repeat的时间,触发一次超时。
#define uv__handle_stop(h) \
do { \
if (((h)->flags & UV_HANDLE_ACTIVE) == 0) break; \
(h)->flags &= ~UV_HANDLE_ACTIVE; \
if (((h)->flags & UV_HANDLE_REF) != 0) uv__active_handle_rm(h); \
} \
while (0)
// 停止一个计时器
int uv_timer_stop(uv_timer_t* handle) {
if (!uv__is_active(handle))
return 0;
// 从最小堆中移除该计时器节点
heap_remove(timer_heap(handle->loop),
(struct heap_node*) &handle->heap_node,
timer_less_than);
// 清除激活状态和handle的active数减一
uv__handle_stop(handle);
return 0;
}
// 重新启动一个计时器,需要设置repeat标记
int uv_timer_again(uv_timer_t* handle) {
if (handle->timer_cb == NULL)
return UV_EINVAL;
// 如果设置了repeat标记说明计时器是需要重复触发的
if (handle->repeat) {
// 先把旧的计时器节点从最小堆中移除,然后再重新开启一个计时器
uv_timer_stop(handle);
uv_timer_start(handle, handle->timer_cb, handle->repeat, handle->repeat);
}
return 0;
}
至此,一个定时器的声明周期结束。loop也恢复初始状态。