编写服务器常常会需要实现定时器功能。windows下有微软封得好好的控件,拖之即用,Linux下面就算了,还是自己动手吧。
虽说Linux提供了基于信号的定时功能(alarm,settimer),但是,考虑到信号是如此的粗暴,还是算了,在写高性能服务器的时候,还是别用了。免得被虐。
既然放弃了系统的定时功能,那么只能在用户空间自己实现了,思路也很简单。维护一个时间和一堆定时器事件,每次时间更新便在那一堆定时器事件中找跟当前时间匹配的事件,触发之。这里两个因数决定了定时器的效率,一个就是更新时间的时机和频率,第二就是事件的插入和查找。
来看看经典的服务器是怎么做的吧,nginx是将事件存放在了一个rbtree中,memcached的定时器是用的libevent的,libevent同样将事件存放在了rbtree中(1.4版以后改成最小堆了),而redis则是将其放在了一个链表中(而且未排序),不过由于redis中的定时器不多,所以作者说:“可以用skiplist来优化,但是意义不大”。至于时间的管理就看场景了,太频繁了浪费cpu,太偶尔了定时器又不准。这里就看开发者对系统的把握了。
下面简单看看nginx中的定时器是如何实现的。
nginx中定时器的实现主要在nginx_event_timer.c和.h中。先来看看事件管理:
初始化:
1
2
3
4
5
6
7
8
9
|
ngx_thread_volatile ngx_rbtree_t ngx_event_timer_rbtree
ngx_int_t
ngx_event_timer_init(ngx_log_t *
log
)
{
ngx_rbtree_init(&ngx_event_timer_rbtree, &ngx_event_timer_sentinel,
ngx_rbtree_insert_timer_value);
return
NGX_OK;
}
|
很直接,初始化一个全局的红黑树。
下面是插入事件(在.h中,内联函数):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
static
ngx_inline
void
ngx_event_add_timer(ngx_event_t *ev, ngx_msec_t timer)
{
ngx_msec_t key;
ngx_msec_int_t diff;
key = ngx_current_msec + timer;
//如果这个事件上上一个定时器的事件跟要设的新定时器相差很小的话
//就复用以前的定时器,毕竟O(logN)的数操作也是要时间的嘛,能省就省了
if
(ev->timer_set) {
/*
* Use a previous timer value if difference between it and a new
* value is less than NGX_TIMER_LAZY_DELAY milliseconds: this allows
* to minimize the rbtree operations for fast connections.
*/
diff = (ngx_msec_int_t) (key - ev->timer.key);
if
(ngx_abs(diff) < NGX_TIMER_LAZY_DELAY) {
return
;
}
//删除以前的定时器
ngx_del_timer(ev);
}
ev->timer.key = key;
//锁一下,添加新的定时器
ngx_mutex_lock(ngx_event_timer_mutex);
ngx_rbtree_insert(&ngx_event_timer_rbtree, &ev->timer);
ngx_mutex_unlock(ngx_event_timer_mutex);
ev->timer_set = 1;
}
|
下面是删除事件(同样在.h中):
1
2
3
4
5
6
7
8
9
10
11
|
static
ngx_inline
void
ngx_event_del_timer(ngx_event_t *ev)
{
ngx_mutex_lock(ngx_event_timer_mutex);
ngx_rbtree_delete(&ngx_event_timer_rbtree, &ev->timer);
ngx_mutex_unlock(ngx_event_timer_mutex);
ev->timer_set = 0;
}
|
同样是直接暴力。锁一下,删除节点。
查找事件同样很直接(在.c中):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
ngx_msec_t
ngx_event_find_timer(
void
)
{
ngx_msec_int_t timer;
ngx_rbtree_node_t *node, *root, *sentinel;
if
(ngx_event_timer_rbtree.root == &ngx_event_timer_sentinel) {
return
NGX_TIMER_INFINITE;
}
ngx_mutex_lock(ngx_event_timer_mutex);
root = ngx_event_timer_rbtree.root;
sentinel = ngx_event_timer_rbtree.sentinel;
//找到所有定时器中最近的那个
node = ngx_rbtree_min(root, sentinel);
ngx_mutex_unlock(ngx_event_timer_mutex);
//如果有将来要触发的,便将时间差算出来,返回
//这个值将影响到事件循环中等待函数(比如epoll_wait)的超时
timer = (ngx_msec_int_t) node->key - (ngx_msec_int_t) ngx_current_msec;
return
(ngx_msec_t) (timer > 0 ? timer : 0);
}
|
最后是超时事件的处理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
void
ngx_event_expire_timers(
void
)
{
ngx_event_t *ev;
ngx_rbtree_node_t *node, *root, *sentinel;
sentinel = ngx_event_timer_rbtree.sentinel;
for
( ;; ) {
ngx_mutex_lock(ngx_event_timer_mutex);
root = ngx_event_timer_rbtree.root;
if
(root == sentinel) {
return
;
}
node = ngx_rbtree_min(root, sentinel);
/* node->key <= ngx_current_time */
if
((ngx_msec_int_t) node->key - (ngx_msec_int_t) ngx_current_msec <= 0)
{
ev = (ngx_event_t *) ((
char
*) node - offsetof(ngx_event_t, timer));
ngx_rbtree_delete(&ngx_event_timer_rbtree, &ev->timer);
ngx_mutex_unlock(ngx_event_timer_mutex);
ev->timer_set = 0;
ev->timedout = 1;
ev->handler(ev);
continue
;
}
break
;
}
ngx_mutex_unlock(ngx_event_timer_mutex);
}
|
这个函数将所有该触发的事件一一触发掉。
好了,nginx中定时器事件的管理就这样了,那么nginx是在什么时候更新时间触发事件的呢?很简单,主要在主事件处理函数中,如果发现定时器事件有事件在pending,就会在wait之后更新一下时间。
ps: 最小堆STL实现: std::make_heap, std::push_heap, std::pop_heap