skynet底层源码阅读(6)-定时器

来源:互联网 发布:网络少年图片 编辑:程序博客网 时间:2024/05/16 05:55

在许多网络库中,都是对定时器管理的部分。常见的定时器管理的实现方式有两种。一种是利用epoll中超时的处理机制来实现,就是将定时器容器中最短的超时的时间作为epoll_wait的超时时间,如果超时了,则遍历定时器容器中超时的定时器,调用超时处理函数。还有一种方式是每隔固定的时间检查定时器容器,然后处理超时的事件。skynet的定时器实现方式就属于第二种。

在skynet启动后,会创建一个时间线程,在Skynet_start.c中,定义了时间 线程中运行的函数:

// 用于定时器,时间线程static void *thread_timer(void *p) {struct monitor * m = p;skynet_initthread(THREAD_TIMER);for (;;) {//处理超时事件,只是像特定的skynet_context发送消息,并没有特定的处理skynet_updatetime();CHECK_ABORT//m->count工作线程数  2500微秒  0.0025秒 wakeup(m,m->count-1);// 只要有一个睡眠线程就唤醒,让工作线程热起来usleep(2500);if (SIG) {signal_hup();SIG = 0;}}// wakeup socket threadskynet_socket_exit();// wakeup all worker threadpthread_mutex_lock(&m->mutex);m->quit = 1;pthread_cond_broadcast(&m->cond);pthread_mutex_unlock(&m->mutex);return NULL;}
该线程主要就是循环调用skynet_updatetime()函数,处理超时事件,然后唤醒工作线程,之后再睡眠一定的时间。

在Skynet_timer.c中有具体的相关实现。
timer结构体存储管理所有的定时器:

//对时间事件管理的抽象结构 struct timer {//near存放的是添加时间节点的时后就是当前时间,即不需要等待的时间节点 //TIME_NEAR   256struct link_list near[TIME_NEAR];// 定时器容器组 存放了不同的定时器容器//TIME_LEVEL  64struct link_list t[4][TIME_LEVEL];// 4级梯队,4级不同的定时器struct spinlock lock;//锁,可能是pthread_mutex_t  或原子操作uint32_t time;//当前已经流过的滴答计数//如果启动时间是  xxxxxx秒 yyyy微秒//化成单位是 0.01秒 //则starttime=xxxxxx*100uint32_t starttime;//开机启动事件(绝对时间)//current = yyyy / 10000uint64_t current;//当前时间,现对于系统的开机时间(相对时间)//运行框架后,初始化时间结构体时的准确时间,单位是0.01秒//或在时间线程中每次调用 skynet_updatetime()时的时间uint64_t current_point;};
其中,
//时间节点struct timer_node {struct timer_node *next;uint32_t expire;// 超时滴答计数 即超时间隔};//时间节点队列struct link_list {struct timer_node head;struct timer_node *tail;};
timer中有定时器节点的链表的数组,相当于hash表。添加定时器的时候,调用

//插入定时器,time的单位是0.01秒,如time=300,表示3秒//time是相对时间,handle表示服务编号intskynet_timeout(uint32_t handle, int time, int session) {//time<=0, 即定时时间为0 立即向handle对应的skynet_context发送消息if (time <= 0) {struct skynet_message message;message.source = 0;message.session = session;message.data = NULL;message.sz = (size_t)PTYPE_RESPONSE << MESSAGE_TYPE_SHIFT;//向handle对应的skynet_context发送消息if (skynet_context_push(handle, &message)) {return -1;}} else{ struct timer_event event;event.handle = handle;event.session = session;//会将event加入到时间节点结构体内存后面,timer_add(TI, &event, sizeof(event), time);}return session;}
如果添加的时候,定时器已经超时,直接处理。skynet的定时器处理并没有直接调用处理函数,而是向注册定时器的服务发送超时消息。handle就是服务的id
struct timer_event {uint32_t handle;int session;};

static voidtimer_add(struct timer *T,void *arg,size_t sz,int time) {//创建mode节点,注意分配的内存是 sizeof(*node)+sz,比timer_node要大struct timer_node *node = (struct timer_node *)skynet_malloc(sizeof(*node)+sz);//将内存加入到后面,就是time_eventmemcpy(node+1,arg,sz);//加锁SPIN_LOCK(T);node->expire=time+T->time;add_node(T,node);SPIN_UNLOCK(T);}

timer_update函数如下

//时间每过一个滴答,执行一次该函数static void timer_update(struct timer *T) {SPIN_LOCK(T);// try to dispatch timeout 0 (rare condition)//从超时列表中取出到时的消息来分发timer_execute(T);// shift time first, and then dispatch timer message//偏移定时器并且分发定时器消息,定时器迁移到它合法的容器位置//会将超时的时间节点存放在near中timer_shift(T);//从超时列表中取出到时的消息来分发timer_execute(T);SPIN_UNLOCK(T);}

timer_execute最终调用disptch_list,向超时的服务发送超时消息。
//处理current时间节点所在链表中的所有时间节点,static inline voiddispatch_list(struct timer_node *current) {do {//得到time_node中的time_event结构体struct timer_event * event = (struct timer_event *)(current+1);struct skynet_message message;message.source = 0;message.session = event->session;message.data = NULL;message.sz = (size_t)PTYPE_RESPONSE << MESSAGE_TYPE_SHIFT;//将消息发送到对应的handle去处理,即就是将message加入到handle对应的skynet_context的消息队列中skynet_context_push(event->handle, &message);struct timer_node * temp = current;current=current->next;skynet_free(temp);} while (current);}

总的来说,skynet的定时器属于某个服务,超时的时候向对应的服务发送超时消息。

原创粉丝点击