定时器方案

定时器主要用于需要使⽤超时机制的功能。定时器的实现有两种方式:⼀种是,⽹络事件和时间事件在⼀个线程当中配合使⽤;例如nginx、redis;第⼆种是,⽹络事件和时间事件在不同线程当中处理;例如skynet。

定时器的设计

接口设计

定时器主要是用在定时任务中,所以主要需要的是添加、删除定时任务,找到最近要发⽣的定时任务和更新检测定时器,这几个接口,当然对于定时器本身,还有初始化、清除定时器资源等接口。

数据结构选择

由于定时器中存在的是一个个定时任务,每个任务都需要有一个时间戳存放任务的过期时间,所以很容易就会想到要使用有序的数据结构,而且增加删除操作不能影响该结构有序。由于定时器中用得最多的操作是找到最近要发⽣的定时任务,所以这里会把这个操作的时间复杂度单独拿出来讨论。

1.红黑树

是一种平衡二叉搜索树,对于增删查,时间复杂度为O(log n);由于最⼩节点为最左侧节点,所以找到最近要发⽣的定时任务的时间复杂度为O(log n)。nginx的定时器使用的是红黑树。

int init_timer() 
{
    ngx_rbtree_init(&timer, &sentinel, ngx_rbtree_insert_timer_value);
    return 0;
}

void add_timer(timer_entry_t *te, uint32_t msec) 
{
    msec += current_time();
    printf("add_timer expire at msec = %u\n", msec);
    te->timer.key = msec;
    ngx_rbtree_insert(&timer, &te->timer);
}

void del_timer(timer_entry_t *te) 
{
    ngx_rbtree_delete(&timer, &te->timer);
}

void expire_timer() 
{
    timer_entry_t *te;
    ngx_rbtree_node_t *sentinel, *root, *node;
    sentinel = timer.sentinel;
    uint32_t now = current_time();
    for (;;) 
    {
        root = timer.root;
        if (root == sentinel) break;
        node = ngx_rbtree_min(root, sentinel);
        if (node->key > now) break;
        printf("touch timer expire time=%u, now = %u\n", node->key, now);
        te = (timer_entry_t *) ((char *) node - offsetof(timer_entry_t, timer));
        te->handler(te);
        ngx_rbtree_delete(&timer, &te->timer);
        free(te);
    }
}

这里基于nginx的红黑树,实现定时器的几个接口。这里再提示一下,红黑树的实现还需要注意是否允许key值重复。

2.最小堆

根节点的key值总是该结构中最小的,对于增查,时间复杂度为O(log n);对于删,时间复杂度为O(n),因为最小堆中数据并不有序,所以需要遍历查找,但是可以通过辅助数据结
构(hashtable来快速索引节点)来加快删除操作;由于最⼩节点为根节点,所以找到最近要发⽣的定时任务的时间复杂度为O(1)。最小堆是使用最广泛的定时器数据结构,左右子树高度差最大为1,插入、删除也比红黑树快。go语言使用最小堆作为定时器。

class MinHeapTimer 
{
public:
    MinHeapTimer() 
    {
        _heap.clear();
        _map.clear();
    }
    static inline int Count() 
    {
        return ++_count;
    }

    int AddTimer(uint32_t expire, TimerHandler cb);
    bool DelTimer(int id);
    void ExpireTimer();
private:
    inline bool _lessThan(int lhs, int rhs) 
    {
        return _heap[lhs]->expire < _heap[rhs]->expire;
    }
    bool _shiftDown(int pos);
    void _shiftUp(int pos);
    void _delNode(TimerNode *node);
private:
    vector<TimerNode*>  _heap;
    map<int, TimerNode*> _map;
    static int _count;
};

int MinHeapTimer::AddTimer(uint32_t expire, TimerHandler cb) 
{
    int64_t timeout = current_time() + expire;
    TimerNode* node = new TimerNode;
    int id = Count();
    node->id = id;
    node->expire = timeout;
    node->cb = cb;
    node->idx = (int)_heap.size();
    _heap.push_back(node);
    _shiftUp((int)_heap.size() - 1);
    _map.insert(make_pair(id, node));
    return id;
}

bool MinHeapTimer::DelTimer(int id)
{
    auto iter = _map.find(id);
    if (iter == _map.end())
        return false;
    _delNode(iter->second);
    return true;
}

void MinHeapTimer::_delNode(TimerNode *node)
{
    int last = (int)_heap.size() - 1;
    int idx = node->idx;
    if (idx != last) 
    {
        std::swap(_heap[idx], _heap[last]);
        _heap[idx]->idx = idx;
        if (!_shiftDown(idx)) 
        {
            _shiftUp(idx);
        }
    }
    _heap.pop_back();
    _map.erase(node->id);
    delete node;
}

void MinHeapTimer::ExpireTimer()
{
    if (_heap.empty()) return;
    uint32_t now = current_time();
    do 
    {
        TimerNode* node = _heap.front();
        if (now < node->expire)
            break;
        for (int i = 0; i < _heap.size();  i++)
            std::cout << "touch    idx: " << _heap[i]->idx 
                << " id: " << _heap[i]->id << " expire: "
                << _heap[i]->expire << std::endl;
        if (node->cb) 
        {
            node->cb(node);
        }
        _delNode(node);
    } while(!_heap.empty());
}

bool MinHeapTimer::_shiftDown(int pos)
{
    int last = (int)_heap.size()-1;
    int idx = pos;
    for (;;) 
    {
        int left = 2 * idx + 1;
        if ((left >= last) || (left < 0)) 
        {
            break;
        }
        int min = left; // left child
        int right = left + 1;
        if (right < last && !_lessThan(left, right)) 
        {
            min = right; // right child
        }
        if (!_lessThan(min, idx)) 
        {
            break;
        }
        std::swap(_heap[idx], _heap[min]);
        _heap[idx]->idx = idx;
        _heap[min]->idx = min;
        idx = min;
    }
    return idx > pos;
}

void MinHeapTimer::_shiftUp(int pos)
{
    for (;;) 
    {
        int parent = (pos - 1) / 2; // parent node
        if (parent == pos || !_lessThan(pos, parent)) 
        {
            break;
        }
        std::swap(_heap[parent], _heap[pos]);
        _heap[parent]->idx = parent;
        _heap[pos]->idx = pos;
        pos = parent;
    }
}

3.跳表

加了索引指针的有序链表,以后再分析。redis中有时会使用。因为redis中定时任务较少,所以一般用的是无序单链表,如果定时任务多,会改用跳表。

4.时间轮

对于增删查,时间复杂度为O(1);查找最⼩节点也为O(1)。操作粒度小,但是删除操作很不方便,因为不知道待删除节点是在什么位置。时间轮可以用于多线程,但是锁的粒度还是整个定时器。linux内核的定时器使用时间轮。时间轮增加操作只从单个定时任务触发,忽略定时任务之间的⼤⼩关系;⽽红⿊树、最⼩堆、跳表的有序性依赖定时任务之间的⼤⼩关系。

timer_node_t * link_clear(link_list_t *list) 
{
	timer_node_t * ret = list->head.next;
	list->head.next = 0;
	list->tail = &(list->head);

	return ret;
}

void link(link_list_t *list, timer_node_t *node) 
{
	list->tail->next = node;
	list->tail = node;
	node->next=0;
}

void add_node(s_timer_t *T, timer_node_t *node) 
{
	uint32_t time=node->expire;
	uint32_t current_time=T->time;
	uint32_t msec = time - current_time;
	if (msec < TIME_NEAR) 
    { //[0, 0x100)
		link(&T->near[time&TIME_NEAR_MASK],node);
	} else if (msec < (1 << (TIME_NEAR_SHIFT+TIME_LEVEL_SHIFT))) 
    {//[0x100, 0x4000)
		link(&T->t[0][((time>>TIME_NEAR_SHIFT) & TIME_LEVEL_MASK)],node);	
	} else if (msec < (1 << (TIME_NEAR_SHIFT+2*TIME_LEVEL_SHIFT))) 
    {//[0x4000, 0x100000)
		link(&T->t[1][((time>>(TIME_NEAR_SHIFT + TIME_LEVEL_SHIFT)) & TIME_LEVEL_MASK)],node);	
	} else if (msec < (1 << (TIME_NEAR_SHIFT+3*TIME_LEVEL_SHIFT))) 
{//[0x100000, 0x4000000)
		link(&T->t[2][((time>>(TIME_NEAR_SHIFT + 2*TIME_LEVEL_SHIFT)) & TIME_LEVEL_MASK)],node);	
	} else {//[0x4000000, 0xffffffff]
		link(&T->t[3][((time>>(TIME_NEAR_SHIFT + 3*TIME_LEVEL_SHIFT)) & TIME_LEVEL_MASK)],node);	
	}
}

timer_node_t* add_timer(int time, handler_pt func, int threadid) 
{
	timer_node_t *node = (timer_node_t *)malloc(sizeof(*node));
	spinlock_lock(&TI->lock);
	node->expire = time+TI->time;// 每10ms加1 0
	node->callback = func;
	node->id = threadid;
	if (time <= 0) 
    {
		node->callback(node);
		free(node);
		spinlock_unlock(&TI->lock);
		return NULL;
	}
	add_node(TI, node);
	spinlock_unlock(&TI->lock);
	return node;
}

void move_list(s_timer_t *T, int level, int idx) 
{
	timer_node_t *current = link_clear(&T->t[level][idx]);
	while (current) 
    {
		timer_node_t *temp=current->next;
		add_node(T,current);
		current=temp;
	}
}

void timer_shift(s_timer_t *T) 
{
	int mask = TIME_NEAR;
	uint32_t ct = ++T->time;
	if (ct == 0) 
    {
		move_list(T, 3, 0);
	} else 
    {
		// ct / 256
		uint32_t time = ct >> TIME_NEAR_SHIFT;
		int i=0;
		// ct % 256 == 0
		while ((ct & (mask-1))==0) 
        {
			int idx=time & TIME_LEVEL_MASK;
			if (idx!=0) 
            {
				move_list(T, i, idx);
				break;				
			}
			mask <<= TIME_LEVEL_SHIFT;
			time >>= TIME_LEVEL_SHIFT;
			++i;
		}
	}
}

void dispatch_list(timer_node_t *current) 
{
	do 
    {
		timer_node_t * temp = current;
		current=current->next;
        if (temp->cancel == 0)
            temp->callback(temp);
		free(temp);
	} while (current);
}

void timer_execute(s_timer_t *T)
{
	int idx = T->time & TIME_NEAR_MASK;
	while (T->near[idx].head.next) 
    {
		timer_node_t *current = link_clear(&T->near[idx]);
		spinlock_unlock(&T->lock);
		dispatch_list(current);
		spinlock_lock(&T->lock);
	}
}

void timer_update(s_timer_t *T) 
{
	spinlock_lock(&T->lock);
	timer_execute(T);
	timer_shift(T);
	timer_execute(T);
	spinlock_unlock(&T->lock);
}

void del_timer(timer_node_t *node) 
{
    node->cancel = 1;
}

s_timer_t * timer_create_timer() 
{
	s_timer_t *r=(s_timer_t *)malloc(sizeof(s_timer_t));
	memset(r,0,sizeof(*r));
	int i,j;
	for (i=0;i<TIME_NEAR;i++) 
    {
		link_clear(&r->near[i]);
	}
	for (i=0;i<4;i++) 
    {
		for (j=0;j<TIME_LEVEL;j++) 
        {
			link_clear(&r->t[i][j]);
		}
	}
	spinlock_init(&r->lock);
	r->current = 0;
	return r;
}

uint64_t gettime() 
{
	uint64_t t;
#if !defined(__APPLE__) || defined(AVAILABLE_MAC_OS_X_VERSION_10_12_AND_LATER)
	struct timespec ti;
	clock_gettime(CLOCK_MONOTONIC, &ti);
	t = (uint64_t)ti.tv_sec * 100;
	t += ti.tv_nsec / 10000000;
#else
	struct timeval tv;
	gettimeofday(&tv, NULL);
	t = (uint64_t)tv.tv_sec * 100;
	t += tv.tv_usec / 10000;
#endif
	return t;
}

void expire_timer(void) 
{
	uint64_t cp = gettime();
	if (cp != TI->current_point)
    {
		uint32_t diff = (uint32_t)(cp - TI->current_point);
		TI->current_point = cp;
		int i;
		for (i=0; i<diff; i++) 
        {
			timer_update(TI);
		}
	}
}

void init_timer(void) 
{
	TI = timer_create_timer();
	TI->current_point = gettime();
}

void clear_timer() 
{
	int i,j;
	for (i=0;i<TIME_NEAR;i++) 
    {
		link_list_t * list = &TI->near[i];
		timer_node_t* current = list->head.next;
		while(current) 
        {
			timer_node_t * temp = current;
			current = current->next;
			free(temp);
		}
		link_clear(&TI->near[i]);
	}
	for (i=0;i<4;i++) 
    {
		for (j=0;j<TIME_LEVEL;j++) 
        {
			link_list_t * list = &TI->t[i][j];
			timer_node_t* current = list->head.next;
			while (current) 
            {
				timer_node_t * temp = current;
				current = current->next;
				free(temp);
			}
			link_clear(&TI->t[i][j]);
		}
	}
}

这里实现的是一个多层级的时间轮,skynet中的定时器就是这样实现的。这里再提示一下,为了避免频繁地创建和销毁内存,如果知道了元素所需最大个数,可以将该元素以数组的形式声明成全局静态变量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值