Linux定时器机制实现循环确定时间
前言
想要实现每循环2个小时就做一次业务处,我的简单思路是直接sleep(2*60*60),但是这样感觉不太好,相当于整个线程会卡在这儿,另一个思路是使用time_t不间断的查询时间进行比较,判断是否达到两小时时间,如下:
static void *update_monitor(void *arg)
{
pthread_detach(pthread_self());
time_t before_time = time(NULL);
int interval_time = 2*60*60;
while(g_mqtt_param.update_flag){
time_t after_time = time(NULL);
if(after_time - before_time >= interval_time){
// 业务操作
before_time = time(NULL);
}
sleep(60);
}
}
解决
使用定时器机制可以更精确地控制任务的执行,而不是每隔固定时间进行 sleep
。在 C 语言中,我们可以使用 POSIX 定时器 (timer_create
, timer_settime
) 来实现定时任务。这个机制相较于使用 sleep()
或者忙等待(polling)更高效,因为它不会阻塞线程,且定时精度高。
下面我会给你一个如何使用定时器机制来每 2 小时执行一次更新子设备操作的示例。
定时器机制实现示例
我们将使用 timer_create()
创建一个定时器,使用 timer_settime()
设置定时器的触发间隔,并用信号处理函数来处理定时器超时事件。
#include <stdio.h>
#include <time.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#define UPDATE_INTERVAL 2 * 60 * 60 // 2 hours in seconds
// 定时器标志,用于控制循环退出
volatile sig_atomic_t timer_expired = 0;
// 定时器到期时的处理函数
void timer_handler(int signum) {
// 定时器到期时会触发此信号
timer_expired = 1;
}
void update_sub_device() {
// 注册定时器信号处理函数
struct sigaction sa;
sa.sa_handler = timer_handler; // 定义定时器到期时的处理函数
sa.sa_flags = SA_RESTART;
sigaction(SIGALRM, &sa, NULL); // SIGALRM 是定时器到期时会发送的信号
// 创建定时器
timer_t timerid;
struct itimerspec its;
its.it_value.tv_sec = UPDATE_INTERVAL; // 设置定时器初始超时为2小时
its.it_value.tv_nsec = 0;
its.it_interval.tv_sec = UPDATE_INTERVAL; // 设置定时器间隔为2小时
its.it_interval.tv_nsec = 0;
// 创建定时器
if (timer_create(CLOCK_REALTIME, NULL, &timerid) == -1) {
perror("timer_create");
exit(EXIT_FAILURE);
}
// 启动定时器
if (timer_settime(timerid, 0, &its, NULL) == -1) {
perror("timer_settime");
exit(EXIT_FAILURE);
}
// 模拟主程序的其他逻辑
while (1) {
if (timer_expired) {
// 2小时到期后,执行更新子设备的操作
printf("Updating sub-device...\n");
// 重置定时器标志
timer_expired = 0;
// 重新启动定时器
if (timer_settime(timerid, 0, &its, NULL) == -1) {
perror("timer_settime");
exit(EXIT_FAILURE);
}
}
// 可以在这里添加其他业务逻辑
sleep(1); // 主程序空闲时可以执行其他任务
}
}
int main() {
update_sub_device();
return 0;
}
代码解析:
-
定时器信号处理:
- 我们使用
sigaction()
注册了一个信号处理器,当定时器超时时,它会发送SIGALRM
信号,信号处理函数timer_handler
会被触发。 timer_expired
变量作为标志,指示定时器是否到期。
- 我们使用
-
创建定时器:
timer_create()
创建了一个定时器,它需要一个时钟(此处使用CLOCK_REALTIME
)和一个timer_t
类型的定时器ID。timer_settime()
设置定时器的超时和间隔时间,it_value
是第一次触发的时间,it_interval
是定时器的周期。
-
定时器执行:
- 在主循环中,我们检查
timer_expired
标志。如果标志为1
,说明定时器已到期,此时执行更新子设备的操作。 - 操作完成后,我们将
timer_expired
置为0
,并使用timer_settime()
重新启动定时器,确保下次定时器再到期时执行操作。
- 在主循环中,我们检查
-
信号和处理函数:
- 当定时器到期时,它会发送一个
SIGALRM
信号,我们的信号处理函数timer_handler()
会被触发,标记timer_expired
为1
。
- 当定时器到期时,它会发送一个
优势:
- 高效性:使用定时器不需要在程序中使用
sleep()
来延时,避免了忙等待或长时间阻塞。 - 精确控制:定时器机制能够提供精确的时间间隔,能够处理精确到秒的定时任务。
注意事项:
- 信号处理:信号处理函数是异步执行的,需要确保它的执行尽可能简单,不要执行耗时操作。复杂的操作应该通过设置标志来交给主线程处理。
- 错误处理:在实际的生产环境中,要更加注意定时器相关函数的错误处理,确保定时器的正确创建和启动。
总结:
使用 POSIX 定时器可以使程序更加高效和精确地执行定时任务。相比于使用 sleep()
,它能够提供更好的时间管理,并且避免了因忙等待导致的资源浪费。