定时器实现方案
1、最小堆 golang的定时器
https://github.com/polaris1119/The-Golang-Standard-Library-by-Example/blob/master/chapter04/04.4.md
用法
fmt.Println(time.Now())
f := func(){
fmt.Println(time.Now(),"hello world")
}
time.AfterFunc(time.Second,f)
time.Sleep(2*time.Second)
fmt.Println(time.Now())
/*
2021-01-31 12:10:31.158899599 +0800 CST m=+0.000195036
2021-01-31 12:10:32.159610127 +0800 CST m=+1.000905972 hello world
2021-01-31 12:10:33.159661235 +0800 CST m=+2.000957358
*/
time.AfterFunc定义在src/time/sleep.go
// AfterFunc waits for the duration to elapse and then calls f
// in its own goroutine. It returns a Timer that can
// be used to cancel the call using its Stop method.
func AfterFunc(d Duration, f func()) *Timer {
t := &Timer{
r: runtimeTimer{
when: when(d),
f: goFunc,
arg: f,
},
}
startTimer(&t.r)
return t
}
startTimer定义在src/runtime/time.go
// startTimer adds t to the timer heap.
//go:linkname startTimer time.startTimer
func startTimer(t *timer) {
//...
addtimer(t)
//...
}
// addtimer adds a timer to the current P.
// This should only be called with a newly created timer.
// That avoids the risk of changing the when field of a timer in some P's heap,
// which could cause the heap to become unsorted.
func addtimer(t *timer) {
//...
doaddtimer(pp, t)
//...
}
// doaddtimer adds t to the current P's heap.
// The caller must have locked the timers for pp.
func doaddtimer(pp *p, t *timer) {
//...
siftupTimer(pp.timers, i)
//...
}
最小堆,不同于平常堆排序用的二叉堆,go用的是4叉堆。定义在src/runtime/time.go
// Heap maintenance algorithms.
// These algorithms check for slice index errors manually.
// Slice index error can happen if the program is using racy
// access to timers. We don't want to panic here, because
// it will cause the program to crash with a mysterious
// "panic holding locks" message. Instead, we panic while not
// holding a lock.
func siftupTimer(t []*timer, i int) {
if i >= len(t) {
badTimer()
}
when := t[i].when
tmp := t[i]
for i > 0 {
p := (i - 1) / 4 // parent
if when >= t[p].when {
break
}
t[i] = t[p]
i = p
}
if tmp != t[i] {
t[i] = tmp
}
}
func siftdownTimer(t []*timer, i int) {
n := len(t)
if i >= n {
badTimer()
}
when := t[i].when
tmp := t[i]
for {
c := i*4 + 1 // left child
c3 := c + 2 // mid child
if c >= n {
break
}
w := t[c].when
if c+1 < n && t[c+1].when < w {
w = t[c+1].when
c++
}
if c3 < n {
w3 := t[c3].when
if c3+1 < n && t[c3+1].when < w3 {
w3 = t[c3+1].when
c3++
}
if w3 < w {
w = w3
c = c3
}
}
if w >= when {
break
}
t[i] = t[c]
i = c
}
if tmp != t[i] {
t[i] = tmp
}
}
2、红黑树 nginx定时器
nginx的红黑树定义在,src/core/ngx_rbtree.h,src/core/ngx_rbtree.c
nginx的定时器模块用到了红黑树,src/event/ngx_event_timer.h,src/event/ngx_event_timer.c
src/event/ngx_event_timer.h
/*
* Copyright (C) Igor Sysoev
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_TIMER_H_INCLUDED_
#define _NGX_EVENT_TIMER_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#define NGX_TIMER_INFINITE (ngx_msec_t) -1
#define NGX_TIMER_LAZY_DELAY 300
ngx_int_t ngx_event_timer_init(ngx_log_t *log);
ngx_msec_t ngx_event_find_timer(void);
void ngx_event_expire_timers(void);
ngx_int_t ngx_event_no_timers_left(void);
extern ngx_rbtree_t ngx_event_timer_rbtree;
static ngx_inline void
ngx_event_del_timer(ngx_event_t *ev)
{
//...
ngx_rbtree_delete(&ngx_event_timer_rbtree, &ev->timer);//删除结点
//...
ev->timer_set = 0;
//...
}
static ngx_inline void
ngx_event_add_timer(ngx_event_t *ev, ngx_msec_t timer)
{
//...
ngx_rbtree_insert(&ngx_event_timer_rbtree, &ev->timer);//添加结点到红黑树中,时间戳作为key
ev->timer_set = 1;
//...
}
#endif /* _NGX_EVENT_TIMER_H_INCLUDED_ */
src/event/ngx_event_timer.c
/*
* Copyright (C) Igor Sysoev
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
ngx_rbtree_t ngx_event_timer_rbtree;
static ngx_rbtree_node_t ngx_event_timer_sentinel;
/*
* the event timer rbtree may contain the duplicate keys, however,
* it should not be a problem, because we use the rbtree to find
* a minimum timer value only
*/
ngx_int_t
ngx_event_timer_init(ngx_log_t *log)//初始化定时器的红黑树树根
{
//...
}
ngx_msec_t
ngx_event_find_timer(void)
{
//...
node = ngx_rbtree_min(root, sentinel);//查找时间戳最小的结点
timer = (ngx_msec_int_t) (node->key - ngx_current_msec);
return (ngx_msec_t) (timer > 0 ? timer : 0);//判断是否到期
}
void
ngx_event_expire_timers(void)
{
//...
for ( ;; ) {
//...
node = ngx_rbtree_min(root, sentinel);//寻找时间戳最小结点
//...
ev = (ngx_event_t *) ((char *) node - offsetof(ngx_event_t, timer));
//...
ngx_rbtree_delete(&ngx_event_timer_rbtree, &ev->timer);//删除结点
//...
ev->timer_set = 0;
ev->timedout = 1;
ev->handler(ev);//执行回调函数
}
}
ngx_int_t
ngx_event_no_timers_left(void)
{
//...
}
红黑树节点在结构体ngx_event_t中,定义在src/event/ngx_event.h
struct ngx_event_s {
//...
ngx_rbtree_node_t timer;
//...
};
3、时间轮 skynet
skynet的时间轮定义在skynet-src/skynet_timer.h,skynet-src/skynet_timer.c
先看看头文件skynet-src/skynet_timer.h,我自己加的注释
#ifndef SKYNET_TIMER_H
#define SKYNET_TIMER_H
#include <stdint.h>
int skynet_timeout(uint32_t handle, int time, int session); //注册定时任务到时间轮
void skynet_updatetime(void); //根据当前时间,执行定时任务
uint32_t skynet_starttime(void);
uint64_t skynet_thread_time(void); // for profile, in micro second
void skynet_timer_init(void); //初始化时间轮全局变量 struct timer * TI
#endif
skynet-src/skynet_timer.c,摘取部分内容,我自己加的注释
#define TIME_NEAR_SHIFT 8
#define TIME_NEAR (1 << TIME_NEAR_SHIFT) //256
#define TIME_LEVEL_SHIFT 6 // |
#define TIME_LEVEL (1 << TIME_LEVEL_SHIFT) // | //64
#define TIME_NEAR_MASK (TIME_NEAR-1) //255 // |
#define TIME_LEVEL_MASK (TIME_LEVEL-1) //65
struct timer {
struct link_list near[TIME_NEAR]; //最近要发生的事 范围0~255,单位1
struct link_list t[4][TIME_LEVEL];//未来要发生的事 t[0],范围256~16383,单位256 ;t[1],范围16384~1048575,单位16384; t[2],范围1048576~67108863,单位1048576; t[3],范围67108864~4294967295,单位67108864
struct spinlock lock;//自旋锁
uint32_t time;//以starttime为起点,从0开始的时间,范围0~4294967295,对应near和t[4],单位是10毫秒
uint32_t starttime;//开始的时间,既初始化时间轮的时间,单位秒
uint64_t current;//当前时间,毫秒,单位10毫秒
uint64_t current_point;//当前时间,秒+毫秒,单位10毫秒
};
static inline struct timer_node *
link_clear(struct link_list *list) {//清空队列list,并返回原队列头,或者可以说是摘取整个队列出来
//...
}
static inline void
link(struct link_list *list,struct timer_node *node) {//队列list添加节点node
//...
}
static void
add_node(struct timer *T,struct timer_node *node) {
uint32_t time=node->expire;//node的到期时间,单位是10毫秒, 注意time>current_time,一定的
uint32_t current_time=T->time;//以starttime为起点,从0开始的时间,范围0~4294967295,对应near和t[4],单位是10毫秒
//这几个区域0~255、256~16383、16384~1048575、1048576~67108863、67108864~4294967295
if ((time|TIME_NEAR_MASK)==(current_time|TIME_NEAR_MASK)) {//判断time和current_time差距在0~255范围内
link(&T->near[time&TIME_NEAR_MASK],node);
} else {
int i;
uint32_t mask=TIME_NEAR << TIME_LEVEL_SHIFT;
for (i=0;i<3;i++) {
if ((time|(mask-1))==(current_time|(mask-1))) {//判断time和current_time差距在256~16383、16384~1048575、1048576~67108863、67108864~4294967295范围内
//因为mask是由小变大的,所以就可以这么区分一段一段
//时间精度不同, 而且队列无序,但不在这里处理
break;
}
mask <<= TIME_LEVEL_SHIFT;
}
link(&T->t[i][((time>>(TIME_NEAR_SHIFT + i*TIME_LEVEL_SHIFT)) & TIME_LEVEL_MASK)],node);//下标是1~63,不包括0的; 这句话有点强,我想想什么意思先
}
}
static void
move_list(struct timer *T, int level, int idx) {//取出队列,并重新加入,就是处理时间精度
struct timer_node *current = link_clear(&T->t[level][idx]);
while (current) {
struct timer_node *temp=current->next;
add_node(T,current);
current=temp;
}
}
static void
timer_shift(struct timer *T) {
int mask = TIME_NEAR;
uint32_t ct = ++T->time;
if (ct == 0) {//等于0,或者是+1溢出又回到0
move_list(T, 3, 0);//???不明白
} else {
uint32_t time = ct >> TIME_NEAR_SHIFT;
int i=0;
while ((ct & (mask-1))==0) {//当T->time加到256(2^8)的倍数时,重新调整对应的队列(处理时间精度)
int idx=time & TIME_LEVEL_MASK;
if (idx!=0) {//下标0的队列是空的//怎么想出来的??
move_list(T, i, idx);
break;
}
mask <<= TIME_LEVEL_SHIFT;
time >>= TIME_LEVEL_SHIFT;
++i;
}
}
}
我想个例子先
当 T->time=0,time=255时,add_node添加节点,到T->near[255]
当 T->time=0,time=256时,add_node添加节点,到T->t[0][1]
过了一段时间...
当 T->time=255,time=255*2时,add_node添加节点,到T->t[0][1]
当T->time=256时,timer_shift重新调整T->t[0][1],time=256调整为T->near[0],time=255*2调整为T->near[255*2-256]
之后add_node添加什么节点,都不会落在T->t[0][1]里
过了一段时间...
当T->time=256*2时,timer_shift重新调整T->t[0][2]
我再想想......
怎么想出来的......厉害啊
时间轮的类似树木年轮(同心圆,半径成倍数关系),从圆心向外扩展,面积越来越大(能容纳的定时任务越来越多),但半径差是一样的(偏移+1)
时间轮和红黑树,最小堆的区别:
1、时间轮可以自定义时间差;红黑树,最小堆是以时间戳为单位的;
2、时间轮的加锁解锁操作快速,主要是对队列头尾结点的操作;红黑树、最小堆需要锁整棵树
3、
时间轮牛逼,谁想出来的