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)!