自学内容网 自学内容网

settimer的坑

现象说明

某监控程序,想要实现间隔3秒做一些事情,间隔1分钟做一些事情,但是实测的时候发现只有最后一个定时器有执行。

代码如下,代码层面上还做了些许重构,将定时器部分进行封装,本意是方便添加定时任务:

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

// 定时器触发时调用的信号处理函数
void timer_handler_world(int sig)
{
    // 打印 "Hello, World"
    printf("Hello, World\n");
}

void timer_handler_c(int sig)
{
    // 打印 "Hello, World"
    printf("Hello, C language\n");
}

void set_timer(timer_t timer_id, int first_timespan_sec, int time_span_sec, __sighandler_t action)
{
    struct sigaction sa;
    struct itimerspec timer_spec;
    // timer_t timer_id;

    // 设置信号处理函数
    sa.sa_handler = action;
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);

    // 注册 SIGALRM 信号
    if (sigaction(SIGALRM, &sa, NULL) == -1)
    {
        perror("sigaction");
        return;
    }

    // 创建定时器
    if (timer_create(CLOCK_REALTIME, NULL, &timer_id) == -1)
    {
        perror("timer_create");
        return;
    }
    printf("timer id: %p\n", timer_id);

    // 设置定时器
    timer_spec.it_value.tv_sec = first_timespan_sec; // 初始延迟 3 秒
    timer_spec.it_value.tv_nsec = 0;
    timer_spec.it_interval.tv_sec = time_span_sec; // 每隔 3 秒触发一次
    timer_spec.it_interval.tv_nsec = 0;

    if (timer_settime(timer_id, 0, &timer_spec, NULL) == -1)
    {
        perror("timer_settime");
        return;
    }
}

int main()
{
    timer_t timer_id_1;
    timer_t timer_id_2;

    set_timer(timer_id_1, 3, 3, timer_handler_world);
    set_timer(timer_id_2, 5, 5, timer_handler_c);
    // 程序将持续运行,直到手动终止
    while (1)
    {
        // 等待定时器信号的到来
        pause();
    }

    return 0;
}

原因分析

使用set_timer的触发是通过内核的信号触发的,信号的响应,是进程级别的动作;对于一个信号的响应,只能绑定一个函数;而本次实现,则是使用一个信号SIGALRM,尝试多次绑定函数,内核在处理信号绑定处理函数,在设计上就是后面绑定函数直接覆盖前面的。

所以,这个问题并不是timer_settime机制问题,而是信号机制使用不当问题。

解决方案

  • 方案一:如果是定时事件之间事件间隔是整数倍关系,则通过添加计数器完成,比如两个计时计时任务,一个1分钟,一个三分钟,那么设置一个1分钟的定时器,每次触发1分钟的任务每次都执行,三分钟的通过分析计数器,只有计数器达到了3次,才会执行,执行完毕后记得计数器清零

  • 方案二:方案一的问题就是扩展性不足,只能满足整数倍的场景;如果不是整数倍关系,那么搞一个最小公约数,比如一个是2分钟,一个是3分钟,那么可以搞一个1分钟的定时器,再维持2个计数器:2分钟计数器和3分钟计数器,每次timer触发,计数器+1,2分钟计数器满足计数达到2则触发,3分钟计数器则是计数达到3则触发

  • 方案三:方案二其实扩展性也是不足的,如果后面再来一个需要半分钟触发的需求怎么办?扩展性最好的方案还是启动一个线程,然后通过让线程休眠指定时长的方式,完成定时任务;但是sleep的方式有一个问题就是时间不准确,因为一个进程sleep的时候,意味着cpu资源已经交出去了,即使计时时间到了,还是需要以来内核进行调度,才能够被唤醒;所以无法保证精确的时延。

扩展

timer_settime注册的定时器,默认是触发SIG_ALARM,如果想要自定义触发信号,可以参考一下实现;核心差别就是在于函数timer_create的第二个参数,上述默认采用SIGALARM信号通知,第二个参数是NULL,而如果想要自定义通知信号,需要为第二个参数,构建一个sigevent结构体,并将自定义的信号赋值给sigevent的sigev_signo成员,然后传入timer_create函数中:

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

// 定时器触发时调用的信号处理函数
void timer_handler_world(int sig){...}

void timer_handler_c(int sig){...}

// 设置定时器函数
void set_timer(timer_t *timer_id, int first_timespan_sec, int time_span_sec, __sighandler_t action, int signal)
{
    struct sigaction sa;
    struct itimerspec timer_spec;
    struct sigevent sev;

    // 设置信号处理函数
    sa.sa_handler = action;
    sa.sa_flags = SA_RESTART; // 确保信号处理后系统调用能继续
    sigemptyset(&sa.sa_mask);

    // 注册信号处理
    if (sigaction(signal, &sa, NULL) == -1)
    {
        perror("sigaction");
        return;
    }
    else
    {
        printf("sigaction create successful!\n");
    }

    sev.sigev_notify = SIGEV_SIGNAL; // 使用信号通知
    sev.sigev_signo = SIGUSR1;       // 自定义触发信号为 SIGUSR1
    // 创建定时器
    if (timer_create(CLOCK_REALTIME, &sev, timer_id) == -1)
    {
        perror("timer_create");
        return;
    }
    printf("timer id: %p\n", *timer_id);

    // 设置定时器
    ... ...
}

int main()
{
    timer_t timer_id_1;
    timer_t timer_id_2;

    // 设置定时器1,使用 SIGUSR1 信号触发
    set_timer(&timer_id_1, 3, 3, timer_handler_world, SIGUSR1);

    // 设置定时器2,使用 SIGUSR2 信号触发
    set_timer(&timer_id_2, 5, 5, timer_handler_c, SIGUSR2);

    // 程序将持续运行,直到手动终止
    while (1)
    {
        // 等待信号
        pause();
        printf("receive signal!\n");
    }

    return 0;
}


原文地址:https://blog.csdn.net/xiashiwendao/article/details/145228779

免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!