C时间轮

看完了《linux高性能服务器编程》对里面的定时器很感兴趣。书中提到三种定时器,分别是:基于升序链表的定时器,基于时间轮的定时器,基于时间堆的定时器。三种定时器的实现书中均是给了C++代码,不过我对C++不太感兴趣,虽然现在在做C++开发,因此写了C版本的。书中定时器只给了封装的定时器类,没有给调用层代码,我是估摸着写了调用层代码。这里做个总结,以后可以翻翻:

基于升序链表的定时器没太大难度,因此也懒得总结了。

说一下时间轮,下面是截的书中的图片

这里写图片描述

时间轮,像轮子一样滚动定时,每滚一个刻度,指针就走一个滴答,滚完一圈,就进入下一圈。因此有了这个概念,时间轮的结构也就出来了:1.齿轮(槽slot),用来标识一个滴答;2.槽间隔(slot interval ),当前槽经过多长时间到下一个槽;3.一圈的槽数量(N);4.当前指针,走一个滴答加一,走完一圈又回到初始位置。

再深入一点,定时器以什么方式添加到槽上?可以看图,每一个槽其实就是一个链表头结点,定时器即添加到所属槽的链表后。这样我们可以对时间轮性能进行分析,SI越小,定时精度越高,如果SI=10s,那么我们指定的定时器只能是10s的倍数;如果N越大,定时器效率越高,这也很好理解,N越小,一圈槽数量越少,那么我们同样添加100个定时器,分配到每个头结点的定时器越多,每一次滴答到时,就遍历当前槽,遍历一次所花时间越多。

如何确定定时器位置?根据定时器到时时间可以计算,例如:定时器超时时间timeout=21s(即21s后触发定时器),当前间隔SI=2s,一圈槽数量N=70,当前指针cur_slot指向第5个槽,我们可以计算出定时器放置的位置,这里需要两个变量,一个rotation指定定时器处于第几圈,一个slot指定定时器处于第几个槽,因此slot = ( cur_slot + timeout / SI ) % N = 15, rotation = timeout / SI / N = 0,即此定时器被放置于15槽的链表后,至于是链表头插还是尾插这个随意,指针滴答到了15槽即触发15槽到时,遍历15槽链表,若rotation=0的表示为当前该触发定时器,若rotation>0的定时器对rotation–(其实很好理解,cur_slot在转当前轮,则不处理后面的轮,只对它的rotation减一就跳过,等到cur_slot转下一圈再判断此定时器)。根据这个计算,如果其它参数不变,现在有一个timeout=161s的定时器,cur_slot=5,我们可以计算出这个定时器的slot=15,rotation=1,正好处于第15槽,但是是下一转触发该触发。

也就是说,如果我们根据以上参数,同时添加一个15s和一个161s定时器,他们都会随时间轮轮转触发到,只不过指针第一次只想15槽时,判断15s的定时器rotation为0,则触发定时器,然后删除定时器,遍历到161s定时器时,rotation=1,执行减1,跳过继续轮转,当cur_slot=70的时候也就是时间轮走过65*2=130s时,时间轮转一圈,cur_slot=0,继续下一圈开始,再走过14*2=28s后,到达15槽,判断161s定时器,rotation=0,触发定时器。

有了这些分析,下面直接贴代码:

#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>

typedef struct client_data {
    int fd;
    time_t tt;
    char buf[512];
    void* data;
}client_data;
typedef struct tw_timer {
    //处于时间轮第几转,即时间轮转多少转
    //此定时器可以处于当前转,若再加上槽
    //即可确定此定时器所处时间轮位置
    int rotation;

    //处于当前时间轮转的第几个槽
    int slot;

    //定时器到时执行的回调函数
    void* (*cb_func)( void* param );

    //用户数据,触发回调任务函数的参数
    struct client_data c_data;

    //这里只需要单向不循环链表即可
    //struct tw_timer* prev;
    struct tw_timer* next;
}tw_timer;
typedef struct timer_manager {
    //时间轮当前槽,每经过一个间隔时间,加一实现轮转动,
    //超过总槽数即归零表示当前轮转完
    int cur_slot;

    //时间轮一转的总槽数,总槽数越大槽链表越短,效率越高
    int slot_num_r;

    //相邻时间槽间隔时间,即时间轮转到下一个槽需要时间,
    //间隔时间越短,精度越高,例如10s,表示定时器支持10s
    //间隔定时器添加,最小支持1s
    int slot_interval;

    //每个时间槽链表头结点,即一个槽管理一条链表,链表
    //添加相同槽数的结点,但转数可能不同
    struct tw_timer* slots_head[512];
}timer_manager;

timer_manager tmanager;

void* ontime_func( void* param )
{
    client_data* data = (client_data*)param;
    time_t tt = time(NULL);
    printf("\n----------------------------------------------------\n");
    printf("\tontime,interval:%d\n", (int)(tt - data->tt));
    printf("\told time:%s", ctime(&data->tt));
    printf("\t%s", data->buf);
    printf("\tcur time:%s", ctime(&tt));
    //getchar();
    printf("----------------------------------------------------\n");

    return NULL;
}
int add_timer( timer_manager* tmanager,
    int timeout, client_data* c_data )
{
    if ( timeout < 0 || !tmanager )
        return -1;

    int tick = 0;           //转动几个槽触发
    int rotation = 0;       //处于时间轮第几转
    int slot = 0;           //距离当前槽相差几个槽

    if ( timeout < tmanager->slot_interval )
        tick = 0;
    else
        tick = timeout / tmanager->slot_interval;

    rotation = tick / tmanager->slot_num_r;
    slot = ( tmanager->cur_slot + tick % tmanager->slot_num_r )
                % tmanager->slot_num_r - 1;

    printf("addtimer-->timeout:%d, rotation:%d,slot:%d\n",
        timeout, rotation, slot);

    tw_timer* tmp_t = (tw_timer*)malloc(sizeof(tw_timer));
    tmp_t->rotation = rotation;

    char buf[100] = {0};
    time_t tt = time(NULL) + timeout;

    sprintf( buf, "set time:%s", ctime(&tt));
    memset( tmp_t->c_data.buf, 0, sizeof(tmp_t->c_data.buf));
    strcpy( tmp_t->c_data.buf, buf );
    tmp_t->slot = slot;
    tmp_t->c_data.tt = time(NULL);
    tmp_t->cb_func = ontime_func;

    if ( !tmanager->slots_head[slot] )
    {
        tmanager->slots_head[slot] = tmp_t;
        tmp_t->next = NULL;
        //printf("[line]:%d\n", __LINE__);
        return 0;
    }
    //printf("[line]:%d\n", __LINE__);
    tmp_t->next = tmanager->slots_head[slot]->next;
    tmanager->slots_head[slot]->next = tmp_t;

    return 0;
}
int del_all_timer( timer_manager* tmanager )
{
    //清除、释放所有定时器,懒得写了
}
int tick( timer_manager* tmanager )
{
    if ( !tmanager )
        return -1;

    tw_timer* tmp = tmanager->slots_head[tmanager->cur_slot];
    tw_timer* p_tmp;

    while ( tmp )
    {
        //rotation减一,当前时间轮转不起作用
        //假设这个tmp指向第0个槽的头,链中某个结点的rotaion为下一圈,
        //即rotation=1,所以这个定时器不起作用,而因为cur_slot不断
        //走动,tmp在当前转不可能再指向这个定时器,下一圈cur_slot
        //为0时能继续判断这个定时器,故实现了定时器处于不同转的判断
        if ( tmp->rotation > 0 )
        {
            tmp->rotation--;
            p_tmp = tmp;
            tmp = tmp->next;
        }
        else
        {
            //否则定时器到时,触发回调函数
            tmp->cb_func( &tmp->c_data );

            //删除此定时器结点
            //吃了没用双向链表的亏,写这么low
            if ( tmp == tmanager->slots_head[tmanager->cur_slot] )
            {
                //printf("[line]:%d\n", __LINE__);
                tmanager->slots_head[tmanager->cur_slot] = tmp->next;
                p_tmp = tmp;
                tmp = tmp->next;
                free( p_tmp );
                p_tmp = NULL;
                p_tmp = tmp;
                //printf("[line]:%d\n", __LINE__);
            }
            else
            {
                p_tmp->next = p_tmp->next->next;
                free( tmp );
                tmp = NULL;
                tmp = p_tmp->next;
            }
        }
    }
    //更新时间轮,转动一个槽,转一圈又从开始转
    tmanager->cur_slot = ++tmanager->cur_slot % tmanager->slot_num_r;

    return 0;
}
int init_t_manager( timer_manager* tmanager,
    int slot_num_r, int slot_interval )
{
    tmanager->cur_slot = 0;
    tmanager->slot_num_r = slot_num_r;
    tmanager->slot_interval = slot_interval;

    return 0;
}
//自己试着写的调用层代码
void alarm_handler( int sig )
{
    time_t tt = time(NULL);
    //printf("timer tick:%s", ctime(&tt));

    int ret = tick( &tmanager );
    if ( ret < 0 )
        printf("tick error\n");

    alarm( tmanager.slot_interval );
}
int main()
{
    time_t tt = time(NULL);

    signal( SIGALRM, alarm_handler );

    //init_t_manager( &tmanager, 60, 10 );
    init_t_manager( &tmanager, 60, 1 );

    add_timer( &tmanager, 6, NULL );
    add_timer( &tmanager, 11, NULL );
    add_timer( &tmanager, 22, NULL );
    add_timer( &tmanager, 33, NULL );
    add_timer( &tmanager, 44, NULL );
    add_timer( &tmanager, 55, NULL );
    add_timer( &tmanager, 66, NULL );
    add_timer( &tmanager, 77, NULL );
    add_timer( &tmanager, 88, NULL );
    add_timer( &tmanager, 99, NULL );
    add_timer( &tmanager, 111, NULL );
    add_timer( &tmanager, 122, NULL );
    add_timer( &tmanager, 133, NULL );
    add_timer( &tmanager, 144, NULL );

    printf("start time:%s\n", ctime(&tt));
    alarm( tmanager.slot_interval );

    while ( 1 )
        sleep( 5 );

    return 0;
}

看以上代码,main函数开始即指定了SI=1s,N=60,并添加了很多定时器,然后开始以SI执行定时,每一次到时就触发滴答函数tick(),如此循环定时触发到时信号就实现了时间轮轮转。

关于代码的思考:这里用了SIGALRM信号,每一次到时,主线程暂停,去执行信号函数内容,如果信号SIGALRM的处理函数太庞大,会影响主线程的任务卡顿,虽然以上代码执行量不大,但为了扩展,我觉得可以将定时器触发执行的操作改为添加任务结点到任务链,这样配合线程池效率会高一点,线程池本身会从任务链取任务结点执行,如果我们的定时处理函数只是往任务链放任务,那性能会高很多,而不是往cb_func里执行具体业务逻辑。

下一篇上时间堆。

  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
1、 设计一个按时间转调度的算法<br><br>提示:<br><br>(1)假设系统有5个进程,每个进程用一个进程控制块PCB来代表。PCB的格式如图1-3所示。<br><br><br>其中,进程名即进程标识。<br><br>链接指针:指出下一个到达进程的进程控制块首地址。按照进程到达的顺序排队。系统设置一个队头和队尾指针分别指向第一个和最后一个进程。新生成的进程放队尾。<br><br>估计运行时间、到达时间以及进程状态一第一题中相同。<br><br>(2)为每个进程任意确定一个要求运行时间和到达时间。<br><br>(3)按照进程到达的先后顺序排成一个循环队列。再设一个队首指针指向第一个到达进程的首址。<br><br>(4)执行处理机调度时,开始选择队首的第一个进程运行。另外再设一个当前运行进程指针,指向当前正在运行的进程。<br><br>(5)由于本实验是模拟实验,所以对被选中进程并不实际启动运行,而只是执行:估计运行时间减1、输出当前运行进程的名字。用这两个操作来模拟进程的一次运行。<br><br>(6)进程运行一次后,以后的调度则将当前指针依次下移一个位置,指向下一个进程,即调整当前运行指针指向该进程的链接指针所指进程,以指示应运行进程。同时还应判断该进程的剩余运行时间是否为零。若不为零,则等待下一的运行;若该进程的剩余运行时间为零,则将该进程的状态置为完成态C,并退出循环队列。<br><br>(7)若就绪队列不为空,则重复上述的步骤(5)和(6),直到所有进程都运行完为止。<br><br>(8)在所设计的调度程序中,应包含显示或打印语句。以便显示或打印每次选中进程的名称及运行一次后队列的变化情况。<br><br><br>/******************************************************************************************<br>*<br>* 实验一 时间转算法模拟程序<br>* writen by daysky<br>* 2007-11-19<br>*<br>********************************************************************************************/<br><br>#include <iostream><br>#include <list><br>#include <string><br>#include <fstream><br><br>using namespace std;<br><br>//控制块结构体<br>struct PCB<br>{<br>char name;//进程名<br>PCB *next;//链接指针<br>int reach_time;//到达时间<br>int left_time;//估计运行时间<br>int run_time;//已运行时间<br>char status;//R就绪 c完成<br>PCB();<br>PCB(char aname,int areach_time,int aleft_time,int arun_time=0,char astatus='R',PCB *anext=NULL);<br>PCB(const PCB &from);<br>};<br>PCB::PCB()<br>{<br>next=NULL;<br>reach_time = -1;<br>left_time = -1;<br>run_time = 0;<br>status = 'R';<br>}<br>PCB::PCB(char aname,int areach_time,int aleft_time,int arun_time,char astatus,PCB *anext)<br>{<br>name = aname;<br>reach_time = areach_time;<br>left_time = aleft_time;<br>run_time = arun_time;<br>status = astatus;<br>next = anext;<br>}<br>//拷贝构造函数<br>PCB::PCB(const PCB &from)<br>{<br>name = from.name;<br>next = NULL;<br>reach_time = from.reach_time;<br>left_time = from.left_time;<br>run_time = 0;<br>status = 'R';<br>}<br>/**<br>* 时间片服务类<br>*<br>*/<br>class TimeServe<br>{<br>private:<br>int systime;<br>list<PCB *> *ready_list,*all_task;<br>int together_time;<br>ofstream fout;<br>public:<br>TimeServe();<br>TimeServe(list<PCB *> *a_all_task,const char *logfile);<br>bool run();<br>void check_task();<br>void run_ready(list<PCB *>::iterator &it);<br>void print_ready();<br>~TimeServe();<br>};<br>TimeServe::TimeServe()<br>{<br>systime=0;<br>together_time = 0;<br>ready_list=new list<PCB *>();<br>all_task=new list<PCB *>();<br>}<br>TimeServe::TimeServe(list<PCB *> *a_all_task,const char *logfile)<br>{<br>systime=0;<br>together_time = 0;<br>ready_list = new list<PCB *>();<br>all_task = a_all_task;<br>fout.open(logfile,ios::trunc);<br>}<br><br>//服务执行总调度<br>bool TimeServe::run()<br>{<br>int num = all_task->size();<br>while(ready_list->empty())<br>{<br> //添加新进程,同时从所有队列中删除刚添加的进程<br> check_task();<br> systime++;//运行直到有任务<br>}<br>list<PCB *>::iterator it=ready_list->begin();<br>do<br>{<br> //打印就绪队列<br> print_ready();<br><br> //执行就绪队列<br> run_ready(it);<br><br> systime++;<br><br> check_task();<br>}while(!ready_list->empty());<br><br>//打印平均周转时间<br>fout << "平均周转时间为:" << together_time/num << "!" << endl;<br>return true;<br>}<br><br>//检查到达的任务,添加到就绪队列的尾部<br>void TimeServe::check_task()<br>{<br>PCB *current;<br>list<PCB *>::iterator it;<br>it = all_task->begin();<br>//这里用循环处理,因为可能有多个同时到达的任务<br>while(it!=all_task->end())<br>{<br> current=(*it);<br> if(current->reach_time==systime)<br> {<br> PCB *a_pcb = new PCB(*current);//复制进程信息<br> a_pcb->status = 'R';<br> ready_list->push_back(a_pcb);//添加在就绪队列的尾部<br> it = all_task->erase(it); //从所有任务中删除这个任务<br> fout << "进程" << a_pcb->name << "在时刻:" << systime << "进入就绪队列!" << endl;<br> }<br> else<br> it++;<br>}<br>}<br><br>//执行就绪队列,运行队列的第一个进程,每次只执行一个时间片<br>void TimeServe::run_ready(list<PCB *>::iterator &it)<br>{<br> if(ready_list->empty()) return;//就绪队列为空就不执行,否则<br><br>PCB *current = (*it);<br><br> current->run_time++;<br>current->left_time --;//执行一次,估计时间减一<br><br>fout << "进程" << current->name << "执行在时刻:" << systime << "!" << endl;<br>fout << "进程" << current->name << "已运行时间:" << current->run_time << "!" << endl;<br>fout << "进程" << current->name << "还剩时间为:" << current->left_time << "!" << endl;<br>//当进程完成,改变进程的状态<br>if(current->left_time == 0)<br>{<br> current->status = 'C';<br> //打印并计算周转时间,systime-1为完成时间<br> fout << "进程" << current->name << "在时刻:" << systime << "结束!" << endl;<br> int a_time = systime-1-current->reach_time;<br> together_time += a_time;<br> fout << "进程" << current->name << "的周转时间为:" << a_time << "!" <<endl;<br><br> it=ready_list->erase(it);//删除这个元素,迭代指下一个<br>}<br>else<br> it++;<br><br> //到尾了就从头开始<br> if(it==ready_list->end())<br> it = ready_list->begin();<br>}<br><br>void TimeServe::print_ready()<br>{<br>fout << "就绪队列中的进程有:";<br>list<PCB *>::iterator it=ready_list->begin();<br>while(it!=ready_list->end())<br>{<br> fout << (*it)->name << "、";<br> it++;<br>}<br>fout << endl;<br>}<br>TimeServe::~TimeServe()<br>{<br>fout.close();<br>}<br>int main()<br>{<br>PCB *a_pcb[5];<br>list<PCB *> *all_task=new list<PCB *>();<br> cout << "正在初始化........" << endl;<br>//五个进程的到达时间各不相同<br>a_pcb[0] = new PCB('A',9,10);<br>a_pcb[1] = new PCB('B',1,30);<br>a_pcb[2] = new PCB('C',3,25);<br>a_pcb[3] = new PCB('D',5,40);<br>a_pcb[4] = new PCB('E',2,33);<br><br> for(int i=0;i<5;i++)<br> {<br> all_task->push_back(a_pcb[i]);<br> }<br><br>TimeServe fs(all_task,"times_log.txt");<br>cout << "正在执行........" << endl;

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值