定时器方案:时间表盘

目录

一:前言

二:手搓时间表盘

1、任务结点,层级,表盘的结构体

2、表盘的初始化

3、添加定时任务

4、删除定时任务

5、检查任务是否超时

6、清空任务


一:前言

我之前有两篇文章是写定时器方案的,大家可以看一看,这篇文章是基于之前的文章写出来的。

二:手搓时间表盘

1、任务结点,层级,表盘的结构体

对于时间表盘,通过之前的讲解,我们可以得知,它类似于钟表的结构,有时分秒以及指针。

表盘:整个时间表盘的结构,它包含层级和任务结点,其中含有三个层级:时分秒。这些层级使用数组表示。还有锁和当前的时间。

层级:层级使用数组表示了,那么他的每个坑位使用一个指针指向这个地址。

任务结点:这些结点包含当前的时间和任务。

struct timer_node {         //表盘上一个一个的结点
    struct timer_node *next;    //指向下一个
    uint32_t expire;            //时间
    handler_pt callback;        //回调函数
    uint8_t cancel;             //是否被删除
}timer_node_t;

typedef struct link_list {
    timer_node_t head;
    timer_node_t *tail;
}link_list_t;

typedef struct timer {
    link_list_t second[SECONDS];        //层级
    link_list_t minute[MINUTES];
    link_list_t hour[HOURS];
    spinlock_t lock;                //自旋锁
    uint32_t time;                  //时间
    time_t current_point;           //当前的时间
}timer_st;

2、表盘的初始化

首先就是要开辟成表盘结构体的内存,然后通过link_clear将每个层级的每个坑位进行初始化操作。大家如果想象不到这个图的话,那就看看这个。

//初始化表盘
void
init_timer(void) {
    TI = create_timer();
    TI->current_point = now_time();
}

//创建表盘
static timer_st *
create_timer() {
    timer_st *r = (timer_st *)malloc(sizeof(timer_st));     //为表盘开辟空间
    memset(r,0,sizeof(*r));

    //下面是进行对时分秒行的初始化,在每个表盘层级的数组坑位中添加一个可以连一串结点的头节点。
    int i;
    for (i=0; i<SECONDS; i++) {
        link_clear(&r->second[i]);
    }
    for (i=0; i<MINUTES; i++) {
        link_clear(&r->minute[i]);
    }
    for (i=0; i<HOURS; i++) {
        link_clear(&r->hour[i]);
    }
    spinlock_init(&r->lock);                //初始化完之后进行初始化这个自旋锁。
    return r;
}

time_t
now_time() {
    struct timespec ti;
    clock_gettime(CLOCK_MONOTONIC, &ti);
    // 1ns = 1/1000000000 s = 1/1000000 ms
    return ti.tv_sec;
}

static 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;
}

3、添加定时任务

        这个添加定时任务的整体就是,先创建一个任务结点,将这个任务节点进行初始化,如添加时间,添加回调函数。紧接着就是判断时间与当前的时间,如果时间是已经过去的时间,那么我们就立刻让此任务执行。

        然而当任务是未来要发生的,那么就要将这个创建好的结点进行插入操作。从图中看的话也就是将这个结点先找到所在的层级,然后所在的位置,在最后连接到一连串的结点中去。

//添加时间任务结点
//整体是:创建时间结点,加锁,对这个结点初始化(时间,回调函数),判断时间,时间为负立即执行,时间为正,则添加到行中去。
timer_node_t*
add_timer(int time, handler_pt func) {      //这个包含时间和回调函数
    timer_node_t *node = (timer_node_t *)malloc(sizeof(*node));         //创建一个时间结点
    spinlock_lock(&TI->lock);           //对这个添加时间结点进行加锁
    node->expire = time+TI->time;       //下面就是存放时间和回调函数
    printf("add timer at %u, expire at %u, now_time at %lu\n", TI->time, node->expire, now_time());
    node->callback = func;
    node->cancel = 0;
    if (time <= 0) {                //如果传入的时间是负数,代表这个任务需要马上执行
        spinlock_unlock(&TI->lock);         //进行解锁,并执行回调函数,并释放这个结点。
        node->callback(node);
        free(node);
        return NULL;
    }                                   //如果是传入正常的时间,那就进行添加这个结点的操作。
    add_node(TI, node);                 //通过这个添加到行层的操作,我们就已经把定时任务添加到不同的行层中去了。
    spinlock_unlock(&TI->lock);
    return node;
}

//下面是添加到行中的操作(时间为正的操作)\
这里举个例子:1时30分30秒,那么它要先存放到1小时的位置,等到时间指针移动到小时这里,那么我们检查这个小时这个结点\
我们要将这个结点进行删除一小时,然后发现时间为30分30秒,我们又重新调整位置,把这个结点取出来,存放到分的层级,以此类推。
static void
add_node(timer_st *T, timer_node_t *node) { //传入哪个表盘的哪个结点
    uint32_t time=node->expire;
    uint32_t current_time=T->time;
    uint32_t msec = time - current_time;        //这里表示间隔的时间,也就是多少时间后触发:6541秒后触发
    if (msec < ONE_MINUTE) {                    //下面的link_to是将新添加的结点存放到层级中去,也就是连接到指定位置的后面
        link_to(&T->second[time % SECONDS], node);          //这里是秒的行层
    } else if (msec < ONE_HOUR) {
        link_to(&T->minute[(uint32_t)(time/ONE_MINUTE) % MINUTES], node);       //分的层级
    } else {
        link_to(&T->hour[(uint32_t)(time/ONE_HOUR) % HOURS], node);                 //时的层级
    }   
    //也就是根据多长时间后触发,根据这个时间,把他存放在哪一层。
}

//将结点连接起来,每次都添加到结点的后面
static void
link_to(link_list_t *list, timer_node_t *node) {    //他这里传入的是层级中的一块位置,添加到这个位置的后面。
    list->tail->next = node;        //挪动尾指针,一直指向行层的末尾。
    list->tail = node;
    node->next=0;       //新结点的后面为空。
}

4、删除定时任务

        这里的删除定时任务很简单,就是将他的cancel值设置为1。它并不是让这个结点消失,如果删除这个结点的话,我们就要按着正常的删除结点的步骤写,很麻烦,如果这样的话,写起来很简单。

//设置他的cancel值,将他默认为被删除
void
del_timer(timer_node_t *node) {
    node->cancel = 1;
}

5、检查任务是否超时

        讲一下这里的流程:当我们执行这个检查的函数时,我们首先会获得时间差,这里的时间差是通过下面休眠得来的。时间差是两秒,至于为什么是两秒,往后看就明白了,这里先记住。

        由于时间差是两秒,那么这个循环也就是循环两次,进入这个timer_update函数后。通过execute函数执行超时的任务,在里面我们可以得到秒数,我们通过这个秒数就可以判断当前的时间是几秒,然后通过找到第几秒的位置。通过link_clear函数得到这一连串任务结点的头部 。这一连串的任务结点就是同一时刻的全部任务,我们通过这个头部,可以完成这一连串的任务,并且释放掉。

        这一秒的任务已经执行完了,那么通过timer_shift中将时间进行++操作,我们就得到下一秒的时间了,时间移动了,那么我们要更新整个时间表盘,也就是重新映射,为什么呢?如果加这一秒正好让时针移动,那么这一小时内的全部任务要向上映射,全部存储到分层级中去。如果是分针移动,那么分的要向上映射到秒中去。

        这里的重新映射也就是将任务结点所在的位置进行调整,将他们全部拿出来然后再重新插入就好了。如果看不懂,可以去看我之前的文章。这里主要是怎么实现。

//检查时间是否超时
void
check_timer(int *stop) {
    while (*stop == 0) {
        time_t cp = now_time();         //获取当前的时间
        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);           //更新时间表盘
            }
        }
        usleep(200000); //为什么要休眠呢?这里休眠会给前面造成时间差,时间差也就是这里的休眠时长:两秒。那为什么两秒呢?\
        上面timer_update中我们执行了两次,其中还加了一秒。\
        如果当前时间为4秒,那我们通过timer_update会直接执行第四秒的任务,并重新映射,然后执行第五秒的任务,然后再循环一次,到第6秒(循环两次)\
        从4秒到了6秒,那我们再次走timer_update,会发现直接再次执行第6秒,这样并没有漏掉时间或者多走时间。\
        并且再次执行第6秒是为了避免这个时候又突然加入这一时刻的任务。          
    }
}

//时间表盘的更新,重新对时间任务进行映射。
static void
timer_update(timer_st *T) {
    spinlock_lock(&T->lock);        //表盘的更新操作要加锁。
    timer_execute(T);               //定时任务的执行
    timer_shift(T);                 //重新映射完
    timer_execute(T);               //再次进行执行的操作,因为在上面重新映射的时候,将时间+1了,所以这一秒也需要进行执行。
    spinlock_unlock(&T->lock);
}

//定时任务的执行,拿出坑位中全部的任务
static void
timer_execute(timer_st *T) {
    //在这里是得到具体的秒数,然后对这一秒的任务进行处理操作。
    uint32_t idx = T->time % SECONDS;       

    //这个函数是执行任务的函数,需要在秒这一层进行搜索,也就是挪动指针,如果发现任务超时,那么就执行。
    while (T->second[idx].head.next) {                  //在这一秒的数组坑位中,他的后面可能有多个同时的任务
        timer_node_t *current = link_clear(&T->second[idx]);   //因此我们需要将这一个坑位中全部的任务进行处理,也就是通过link_clear拿出全部的结点。
        spinlock_unlock(&T->lock);
        dispatch_list(current);                 //将这里面全部的任务进行执行。
        spinlock_lock(&T->lock);
    }
}

//处理任务,将拿出的任务全部进行处理
static 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);
}


//这里就是重新映射层级
static void
timer_shift(timer_st *T) {
    //因为咱们存储的时是12小时,对于下午几点的时间要进行单独的设置,比如这里,通过加一,然后对半天的时间取余操作,就得到了下午的时间。
    //下面的操作就是重新映射所在的位置。时间在增加,每次根据增加的时间,来进行重新映射所在的位置。
    uint32_t ct = ++T->time % HALF_DAY;     //++后得到下一秒的时间,因此需要再次执行一遍timer_execute。       
    if (ct == 0) {                  //如果正好是12点整,那么就存放到小时的第0号位置。
        remap(T, T->hour, 0);
    } 
    else {                                          //其他时间
        if (ct % SECONDS == 0) {                    //秒是最上面的层级,不需要进行调整,
            {
                uint32_t idx = (uint32_t)(ct / ONE_MINUTE) % MINUTES;
                if (idx != 0) {
                    remap(T, T->minute, idx);
                    return;
                }
            }
            {
                uint32_t idx = (uint32_t)(ct / ONE_HOUR) % HOURS;
                if (idx != 0) {
                    remap(T, T->hour, idx);
                }
            }
        }
    }
}

6、清空任务

这个清空也很简单,就是将每个坑位全部提取出来然后释放掉就可以。

//将全部的任务结点进行删除
void
clear_timer() {
    int i;
    for (i=0; i<SECONDS; i++) {                 //遍历整个层级
        link_list_t * list = &TI->second[i];    //获得层级中的每一个坑位
        timer_node_t* current = list->head.next;
        while(current) {                        //通过循环将这个坑位中的一串任务结点全部清空。
            timer_node_t * temp = current;
            current = current->next;
            free(temp);
        }
        link_clear(&TI->second[i]);         //清空任务结点
    }                                               //下面也是一样
    for (i=0; i<MINUTES; i++) {
        link_list_t * list = &TI->minute[i];
        timer_node_t* current = list->head.next;
        while(current) {
            timer_node_t * temp = current;
            current = current->next;
            free(temp);
        }
        link_clear(&TI->minute[i]);
    }
    for (i=0; i<HOURS; i++) {
        link_list_t * list = &TI->hour[i];
        timer_node_t* current = list->head.next;
        while(current) {
            timer_node_t * temp = current;
            current = current->next;
            free(temp);
        }
        link_clear(&TI->hour[i]);
    }
}

今天的讲解结束了,看不懂的可以去先看之前的文章,将大概梳理清楚,然后再来看这篇实现文章。https://xxetb.xetslk.com/s/2D96kH

  • 34
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SQLAlchemy 是一个 SQL 工具包和对象关系映射(ORM)库,用于 Python 编程语言。它提供了一个高级的 SQL 工具和对象关系映射工具,允许开发者以 Python 类和对象的形式操作数据库,而无需编写大量的 SQL 语句。SQLAlchemy 建立在 DBAPI 之上,支持多种数据库后端,如 SQLite, MySQL, PostgreSQL 等。 SQLAlchemy 的核心功能: 对象关系映射(ORM): SQLAlchemy 允许开发者使用 Python 类来表示数据库表,使用类的实例表示表中的行。 开发者可以定义类之间的关系(如一对多、多对多),SQLAlchemy 会自动处理这些关系在数据库中的映射。 通过 ORM,开发者可以像操作 Python 对象一样操作数据库,这大大简化了数据库操作的复杂性。 表达式语言: SQLAlchemy 提供了一个丰富的 SQL 表达式语言,允许开发者以 Python 表达式的方式编写复杂的 SQL 查询。 表达式语言提供了对 SQL 语句的灵活控制,同时保持了代码的可读性和可维护性。 数据库引擎和连接池: SQLAlchemy 支持多种数据库后端,并且为每种后端提供了对应的数据库引擎。 它还提供了连接池管理功能,以优化数据库连接的创建、使用和释放。 会话管理: SQLAlchemy 使用会话(Session)来管理对象的持久化状态。 会话提供了一个工作单元(unit of work)和身份映射(identity map)的概念,使得对象的状态管理和查询更加高效。 事件系统: SQLAlchemy 提供了一个事件系统,允许开发者在 ORM 的各个生命周期阶段插入自定义的钩子函数。 这使得开发者可以在对象加载、修改、删除等操作时执行额外的逻辑。
SQLAlchemy 是一个 SQL 工具包和对象关系映射(ORM)库,用于 Python 编程语言。它提供了一个高级的 SQL 工具和对象关系映射工具,允许开发者以 Python 类和对象的形式操作数据库,而无需编写大量的 SQL 语句。SQLAlchemy 建立在 DBAPI 之上,支持多种数据库后端,如 SQLite, MySQL, PostgreSQL 等。 SQLAlchemy 的核心功能: 对象关系映射(ORM): SQLAlchemy 允许开发者使用 Python 类来表示数据库表,使用类的实例表示表中的行。 开发者可以定义类之间的关系(如一对多、多对多),SQLAlchemy 会自动处理这些关系在数据库中的映射。 通过 ORM,开发者可以像操作 Python 对象一样操作数据库,这大大简化了数据库操作的复杂性。 表达式语言: SQLAlchemy 提供了一个丰富的 SQL 表达式语言,允许开发者以 Python 表达式的方式编写复杂的 SQL 查询。 表达式语言提供了对 SQL 语句的灵活控制,同时保持了代码的可读性和可维护性。 数据库引擎和连接池: SQLAlchemy 支持多种数据库后端,并且为每种后端提供了对应的数据库引擎。 它还提供了连接池管理功能,以优化数据库连接的创建、使用和释放。 会话管理: SQLAlchemy 使用会话(Session)来管理对象的持久化状态。 会话提供了一个工作单元(unit of work)和身份映射(identity map)的概念,使得对象的状态管理和查询更加高效。 事件系统: SQLAlchemy 提供了一个事件系统,允许开发者在 ORM 的各个生命周期阶段插入自定义的钩子函数。 这使得开发者可以在对象加载、修改、删除等操作时执行额外的逻辑。
SQLAlchemy 是一个 SQL 工具包和对象关系映射(ORM)库,用于 Python 编程语言。它提供了一个高级的 SQL 工具和对象关系映射工具,允许开发者以 Python 类和对象的形式操作数据库,而无需编写大量的 SQL 语句。SQLAlchemy 建立在 DBAPI 之上,支持多种数据库后端,如 SQLite, MySQL, PostgreSQL 等。 SQLAlchemy 的核心功能: 对象关系映射(ORM): SQLAlchemy 允许开发者使用 Python 类来表示数据库表,使用类的实例表示表中的行。 开发者可以定义类之间的关系(如一对多、多对多),SQLAlchemy 会自动处理这些关系在数据库中的映射。 通过 ORM,开发者可以像操作 Python 对象一样操作数据库,这大大简化了数据库操作的复杂性。 表达式语言: SQLAlchemy 提供了一个丰富的 SQL 表达式语言,允许开发者以 Python 表达式的方式编写复杂的 SQL 查询。 表达式语言提供了对 SQL 语句的灵活控制,同时保持了代码的可读性和可维护性。 数据库引擎和连接池: SQLAlchemy 支持多种数据库后端,并且为每种后端提供了对应的数据库引擎。 它还提供了连接池管理功能,以优化数据库连接的创建、使用和释放。 会话管理: SQLAlchemy 使用会话(Session)来管理对象的持久化状态。 会话提供了一个工作单元(unit of work)和身份映射(identity map)的概念,使得对象的状态管理和查询更加高效。 事件系统: SQLAlchemy 提供了一个事件系统,允许开发者在 ORM 的各个生命周期阶段插入自定义的钩子函数。 这使得开发者可以在对象加载、修改、删除等操作时执行额外的逻辑。
SQLAlchemy 是一个 SQL 工具包和对象关系映射(ORM)库,用于 Python 编程语言。它提供了一个高级的 SQL 工具和对象关系映射工具,允许开发者以 Python 类和对象的形式操作数据库,而无需编写大量的 SQL 语句。SQLAlchemy 建立在 DBAPI 之上,支持多种数据库后端,如 SQLite, MySQL, PostgreSQL 等。 SQLAlchemy 的核心功能: 对象关系映射(ORM): SQLAlchemy 允许开发者使用 Python 类来表示数据库表,使用类的实例表示表中的行。 开发者可以定义类之间的关系(如一对多、多对多),SQLAlchemy 会自动处理这些关系在数据库中的映射。 通过 ORM,开发者可以像操作 Python 对象一样操作数据库,这大大简化了数据库操作的复杂性。 表达式语言: SQLAlchemy 提供了一个丰富的 SQL 表达式语言,允许开发者以 Python 表达式的方式编写复杂的 SQL 查询。 表达式语言提供了对 SQL 语句的灵活控制,同时保持了代码的可读性和可维护性。 数据库引擎和连接池: SQLAlchemy 支持多种数据库后端,并且为每种后端提供了对应的数据库引擎。 它还提供了连接池管理功能,以优化数据库连接的创建、使用和释放。 会话管理: SQLAlchemy 使用会话(Session)来管理对象的持久化状态。 会话提供了一个工作单元(unit of work)和身份映射(identity map)的概念,使得对象的状态管理和查询更加高效。 事件系统: SQLAlchemy 提供了一个事件系统,允许开发者在 ORM 的各个生命周期阶段插入自定义的钩子函数。 这使得开发者可以在对象加载、修改、删除等操作时执行额外的逻辑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值