linux网络编程二十二:高性能定时器之时间轮

来源:互联网 发布:出售淘宝0心店铺 编辑:程序博客网 时间:2024/05/21 17:07

之前我们提到,基于排序链表的定时器存在一个问题:添加定时器的效率偏低。下面我们讨论的时间轮解决了这个问题。

如图,这是一种简单的时间轮:


轮中的实线指针指向轮子上的一个槽(slot),它以恒定的速度顺时针转动,每转动一步就指向下一个槽,每次转动称为一个滴答(tick)。

一个滴答的时间称为是间轮的槽间隔si(slot interval),它实际上就是心跳时间。

该轮共有N个槽,因此它运转一周的时间是N×si 。每个槽指向一条定时器链表,每条链表上的定时器具有相同的特性:它们的定时时间相差N×si的整数倍。时间轮正是利用这个关系将定时器散列到不同的链表中。

假如现在指针指向槽cs,我们要添加一个定时时间为ti的定时器,则该定时器将被插入ts(timer slot)对应的链表中:ts = (cs + (ti / si)) %N

基于排序链表的定时器使用唯一的一条链表来管理所有定时器,所以插入操作的效率随着定时器数目的增多而降低。而时间轮使用哈希表的思想,将定时器散列到不同的链表上。

这样每条链表上的定时数目都将明显减少,插入操作的效率受定时器数目的影响较少。

很显然,对时间轮而言,要提高定时精度,就要使si值足够小;要提高执行效率,则要求N值足够大。


下面我们实现一个简单的时间轮,只实现一个轮子。复杂的时间轮可以有多个,不同的轮子有不同粒度。相邻的两个轮子,精度高的转一圈,精度低的仅往前移动一槽,就像水表一样。

对时间轮而言,添加一个定时器的时间复杂度是O(1), 删除一个定时器的时间复杂度是O(1),执行一个定时器的时间复杂度是O(n)。

但实际上执行一个定时器的效率要比O(n)高得多,因为时间轮将所有的定时器散列到了不同的链表上,时间轮的槽越多,等于散列表的入口越多,从而每条链表上的定时器数量越少。此外,我们的代码仅用了一个时间轮,当使用多个轮子来实现时,它的时间复杂度将接近O(1)。

关于linux下定时器的实现方式对比,大家可以看看这篇文章,写的挺不错的:http://www.ibm.com/developerworks/cn/linux/l-cn-timers/


1. 代码:

//tw_timer.h#ifndef __TIME_WHEEL__#define __TIME_WHEEL__#include <time.h>#include <netinet/in.h>#include <stdio.h>#define BUFFER_SIZE 64class tw_timer;//客户端数据struct client_data{    sockaddr_in address;    int sockfd;    char buf[BUFFER_SIZE];    tw_timer *timer;};//定时器class tw_timer{public:    tw_timer(int rot, int ts)        :next(NULL), prev(NULL), rotation(rot), time_slot(ts) {}public:    int rotation;                       //定时器在时间轮上转多少圈后生效    int time_slot;                      //定时器属于时间轮上的哪个槽    void (*cb_func)(client_data*);      //定时器的回调函数    client_data *user_data;             //客户端数据        tw_timer *prev;                     //指向上一个定时器    tw_timer *next;                     //指向下一个定时器};//时间轮class time_wheel{public:    time_wheel();    ~time_wheel();    tw_timer* add_timer(int timeout);   //根据定时值创建定时器,并插入合适的位置    void del_timer(tw_timer *timer);    //删除目标定时器    void tick();                //时间到后调用该函数,时间轮向前滚动一个槽间隔    private:    static const int N = 60;    //时间轮上槽的数目    static const int TI = 1;    //槽间隔时间,即每1秒时间轮转动一次    int cur_slot;               //时间轮的当前槽    tw_timer *slots[N];         //时间轮的槽,其中每个元素指向一个定时器链表};#endif


//tw_timer.cpp#include "tw_timer.h"time_wheel::time_wheel():cur_slot(0){    //初始化每个槽的头结点    for (int i = 0; i < N; ++i)        slots[i] = NULL;}time_wheel::~time_wheel(){    //遍历每个槽,并销毁定时器    for (int i = 0; i < N; ++i) {        tw_timer *tmp = slots[i];        while (tmp) {            slots[i] = tmp->next;            delete tmp;            tmp = slots[i];        }    }}tw_timer* time_wheel::add_timer(int timeout){    if (timeout < 0)        return NULL;    int ticks = 0;              //待插入定时器所需要总ticks    if (timeout < TI)        ticks = 1;    else        ticks = timeout / TI;    int rotation = ticks / N;   //计算待插入的定时器在时间轮上要转动多少圈后触发    int ts = (cur_slot + ticks) % N; //计算待持入定时器应该被插入的位置    //int ts = (cur_slot + (ticks %N)) % N;    //创建定时器,它在时间轮转动rotation圈之后触发,且位于第ts个槽上    tw_timer *timer = new tw_timer(rotation, ts);    //如果槽为空,则它新定时器插入,并设置为该槽的头节点    if (!slots[ts]) {        printf("add timer, rotation is %d, ts is %d, cur_slot is %d\n",                rotation, ts, cur_slot);        slots[ts] = timer;    }    else {        timer->next = slots[ts];        slots[ts]->prev = timer;        slots[ts] = timer;    }        return timer;}void time_wheel::del_timer(tw_timer *timer){    if (!timer)        return;    int ts = timer->time_slot;    if (timer == slots[ts]) {   //如果是头结点        slots[ts] = slots[ts]->next;        if (slots[ts])            slots[ts]->prev = NULL;        delete timer;    }    else {        timer->prev->next = timer->next;        if (timer->next)            timer->next->prev = timer->prev;        delete timer;    }}void time_wheel::tick(){   //取得时间轮上当前槽的头结点   tw_timer *tmp = slots[cur_slot];   printf("current slot is %d\n", cur_slot);   while (tmp) {       printf("tick the timer once\n");       //如果定时器的rotation值大于0,则未到时,不处理       if (tmp->rotation > 0) {           tmp->rotation--;           tmp = tmp->next;       }       else {           tmp->cb_func(tmp->user_data);           if (tmp == slots[cur_slot]) {               printf("delete header in cur_slot\n");               slots[cur_slot] = tmp->next;               delete tmp;               if (slots[cur_slot])                   slots[cur_slot]->prev = NULL;               tmp = slots[cur_slot];           }           else {               tmp->prev->next = tmp->next;               if (tmp->next)                   tmp->next->prev = tmp->prev;               tw_timer *tmp2 = tmp->next;               delete tmp;               tmp = tmp2;            }       }   }   //更新时间轮的当前槽,以反映时间轮的转动   cur_slot = ++cur_slot % N;}    


//nonactive_conn.cpp//关闭非活动连接#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <errno.h>#include <fcntl.h>#include <sys/epoll.h>#include <assert.h>#include <signal.h>#include <pthread.h>#include "tw_timer.h"#define FD_LIMIT 65535#define MAX_EVENT_NUMBER 1024#define TIMESLOT 100static int pipefd[2];static time_wheel client_conn_time_wheel;static int epollfd = 0;int setnonblocking(int fd);             //设置非阻塞int addfd(int epollfd, int fd);         //添加描述符事件void sig_handler(int sig);              //信号处理函数void addsig(int sig);                   //添加信号处理函数void timer_handler();                   //定时器任务void cb_func(client_data *user_data);   //定时器回调函数int main(int argc, char *argv[]){    if (argc != 2) {        fprintf(stderr, "Usage: %s port\n", argv[0]);        return 1;    }    int port = atoi(argv[1]);    int ret = 0;    int error;    struct sockaddr_in address;    bzero(&address, sizeof(address));    address.sin_family = AF_INET;    address.sin_port = htons(port);    address.sin_addr.s_addr = htonl(INADDR_ANY);    int sockfd = socket(PF_INET, SOCK_STREAM, 0);    if (sockfd == -1) {        fprintf(stderr, "create socket failed\n");        return 1;    }    int reuse = 1;    ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));    if (ret == -1) {        error = errno;        while ((close(sockfd) == -1) && (errno == EINTR));        errno = error;        return 1;    }    if ( (bind(sockfd, (struct sockaddr*)&address, sizeof(address)) == -1) ||         (listen(sockfd, 5) == -1)) {        error = errno;        while ((close(sockfd) == -1) && (errno ==  EINTR));        errno = error;        return 1;    }    int epollfd = epoll_create(5);    if (epollfd == -1) {        error = errno;        while ((close(sockfd)) && (errno == EINTR));        errno = error;        return 1;    }ret = socketpair(PF_UNIX, SOCK_STREAM, 0, pipefd);if (ret == -1) {error = errno;while ((close(sockfd) == -1) && (errno == EINTR));errno = error;return 1;}    epoll_event events[MAX_EVENT_NUMBER];        setnonblocking(pipefd[1]);    addfd(epollfd, pipefd[0]);addfd(epollfd, sockfd);    //添加信号处理    addsig(SIGALRM);    addsig(SIGTERM);    bool stop_server = false;    client_data *users = new client_data[FD_LIMIT];    bool timeout = false;    alarm(1);    printf("server start...\n");    while (!stop_server) {        int number = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);        if (number < 0 && errno != EINTR) {            fprintf(stderr, "epoll_wait failed\n");            break;        }        for (int i = 0; i < number; i++) {            int listenfd = events[i].data.fd;                        if (listenfd == sockfd) {                struct sockaddr_in client_address;                socklen_t client_addrlength = sizeof(client_address);                int connfd;                while ( ((connfd = accept(listenfd, (struct sockaddr*)&client_address, &client_addrlength)) == -1) &&                        (errno == EINTR));                addfd(epollfd, connfd);                users[connfd].address = client_address;                users[connfd].sockfd = connfd;                                tw_timer *timer = NULL;                timer = client_conn_time_wheel.add_timer(TIMESLOT);                if (timer) {                    timer->user_data = &users[connfd];                    timer->cb_func = cb_func;users[connfd].timer = timer;  fprintf(stderr, "client: %d add tw_timer successed\n", connfd);                }                else {                    fprintf(stderr, "client: %d add tw_timer failed\n", connfd);                }            }            else if((listenfd == pipefd[0]) && (events[i].events & EPOLLIN)) {                int sig;                char signals[1024];        ret = recv(pipefd[0], signals, sizeof(signals), 0);                if (ret == -1) {                    continue;                }                else if (ret == 0) {                    continue;                }                else {                    for (int i = 0; i < ret; i++) {                        switch (signals[i]) {                        case SIGALRM:                            {                                timeout = true;                                break;                            }                        case SIGTERM:                            {                                stop_server = true;                                break;                            }                        default:                            break;                        }                    }                }            }            else if (events[i].events & EPOLLIN) {        memset(users[listenfd].buf, '\0', BUFFER_SIZE);                ret = recv(listenfd, users[listenfd].buf, BUFFER_SIZE-1, 0);                printf("get %d bytes of client data: %s from %d\n",                        ret, users[listenfd].buf, listenfd);                tw_timer *timer = users[listenfd].timer;                if (ret < 0) {                    if (errno != EAGAIN) {                        cb_func(&users[listenfd]);                        if (timer)                            client_conn_time_wheel.del_timer(timer);                    }                }                else if (ret == 0) {                    cb_func(&users[listenfd]);                    if (timer)                        client_conn_time_wheel.del_timer(timer);                }                else {                    if (timer) {                        printf("conntioned..to do adjuest timer\n");                    }                }            }            else {                        }                     }                if (timeout) {        timer_handler();            timeout = false;        }    }        close(sockfd);    close(pipefd[1]);    close(pipefd[0]);    delete[] users;    return 0;}int setnonblocking(int fd){    int old_option = fcntl(fd, F_GETFL);    int new_option = old_option | O_NONBLOCK;    fcntl(fd, F_SETFL, new_option);    return old_option;}int addfd(int epollfd, int fd){    epoll_event event;    event.data.fd = fd;    event.events = EPOLLIN | EPOLLET;    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);    setnonblocking(fd);}void sig_handler(int sig){    int save_error = errno;    int msg = sig;    send(pipefd[1], (char*)&msg, 1, 0);    errno = save_error;}void addsig(int sig){    struct sigaction sa;    memset(&sa, '\0', sizeof(sa));    sa.sa_handler = sig_handler;    sa.sa_flags |= SA_RESTART;    sigfillset(&sa.sa_mask);        assert(sigaction(sig, &sa, NULL) != -1);}void timer_handler(){    client_conn_time_wheel.tick();    alarm(1);}void cb_func(client_data *user_data){    epoll_ctl(epollfd, EPOLL_CTL_DEL, user_data->sockfd, 0);    assert(user_data);    close(user_data->sockfd);    printf("close fd %d\n", user_data->sockfd);}


参考:《linux高性能服务器编程》




0 0