定时器设计

定时器实现方案

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、

 

时间轮牛逼,谁想出来的

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值