MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Linux C语言定时器的定时器组管理

2024-03-316.5k 阅读

Linux C 语言定时器的定时器组管理

定时器在 Linux C 编程中的重要性

在 Linux 环境下进行 C 语言编程时,定时器是一个非常重要的工具。它广泛应用于各种场景,比如周期性的数据采集、网络心跳检测、任务调度等。通过设置定时器,程序可以在指定的时间间隔执行特定的任务,实现异步操作,提高系统的响应能力和效率。

简单定时器实现原理

在 Linux 中,最基本的定时器实现是通过 setitimer 函数。setitimer 函数可以设置一个定时器,该定时器会在指定的时间后向进程发送一个信号。以下是 setitimer 函数的原型:

#include <sys/time.h>

int setitimer(int which, const struct itimerval *new_value,
              struct itimerval *old_value);

which 参数指定定时器类型,常见的有 ITIMER_REAL(实时定时器,以系统真实时间计时,超时发送 SIGALRM 信号)、ITIMER_VIRTUAL(以进程用户态运行时间计时,超时发送 SIGVTALRM 信号)和 ITIMER_PROF(以进程用户态和内核态运行时间之和计时,超时发送 SIGPROF 信号)。

new_value 是一个指向 itimerval 结构体的指针,用于设置定时器的初始值和周期性间隔。itimerval 结构体定义如下:

struct itimerval {
    struct timeval it_interval;  /* 下一次定时值 */
    struct timeval it_value;     /* 当前定时值 */
};

struct timeval {
    time_t      tv_sec;         /* 秒 */
    suseconds_t tv_usec;        /* 微秒 */
};

old_value 是一个可选参数,如果不为 NULL,函数会将定时器原来的设置值存储到该指针指向的 itimerval 结构体中。

下面是一个简单的使用 setitimer 设置实时定时器的示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/time.h>

void handle_alarm(int signum) {
    printf("Alarm signal received.\n");
}

int main() {
    struct itimerval new_value;
    struct itimerval old_value;

    // 初始化信号处理函数
    signal(SIGALRM, handle_alarm);

    // 设置定时器初始值为 2 秒,间隔为 1 秒
    new_value.it_value.tv_sec = 2;
    new_value.it_value.tv_usec = 0;
    new_value.it_interval.tv_sec = 1;
    new_value.it_interval.tv_usec = 0;

    // 设置定时器
    if (setitimer(ITIMER_REAL, &new_value, &old_value) == -1) {
        perror("setitimer");
        return 1;
    }

    // 主循环,防止程序退出
    while (1) {
        // 这里可以执行其他任务
    }

    return 0;
}

上述代码中,首先定义了一个信号处理函数 handle_alarm 来处理 SIGALRM 信号。然后在 main 函数中设置了一个实时定时器,初始延迟 2 秒,之后每隔 1 秒触发一次 SIGALRM 信号,在信号处理函数中打印一条信息。主循环用于防止程序在设置定时器后立即退出。

定时器组管理的需求

在实际应用中,一个程序往往需要多个定时器来完成不同的任务。简单地使用单个 setitimer 可能无法满足复杂的定时需求。例如,在一个网络服务器程序中,可能需要一个定时器来检测连接的心跳,另一个定时器来定期清理过期的会话,还可能需要定时器来进行周期性的数据备份等。如果每个定时器都单独使用 setitimer,会导致代码复杂且难以维护。

定时器组管理可以有效地解决这些问题。通过将多个定时器组织成一个组,可以统一管理这些定时器的创建、启动、停止和销毁等操作,提高代码的可维护性和可读性。

定时器组数据结构设计

为了实现定时器组管理,首先需要设计一个合适的数据结构来表示定时器和定时器组。

定时器结构体

typedef struct timer {
    unsigned int id;            // 定时器唯一标识
    struct itimerval value;     // 定时器值
    void (*callback)(void);     // 定时器触发时执行的回调函数
    struct timer *next;         // 指向下一个定时器的指针
} timer_t;

在这个结构体中,id 用于唯一标识每个定时器,valueitimerval 结构体,存储定时器的定时值,callback 是一个函数指针,指向定时器触发时要执行的回调函数,next 用于将多个定时器链接成链表。

定时器组结构体

typedef struct timer_group {
    timer_t *head;              // 定时器链表头
    int count;                  // 定时器数量
} timer_group_t;

timer_group 结构体包含一个指向定时器链表头的指针 head 和一个计数器 count,用于记录定时器组中定时器的数量。

定时器组操作函数实现

创建定时器组

timer_group_t* create_timer_group() {
    timer_group_t *group = (timer_group_t*)malloc(sizeof(timer_group_t));
    if (group == NULL) {
        perror("malloc");
        return NULL;
    }
    group->head = NULL;
    group->count = 0;
    return group;
}

该函数通过 malloc 分配内存创建一个新的定时器组,并初始化链表头为 NULL,计数器为 0。

创建定时器

timer_t* create_timer(unsigned int id, struct itimerval value, void (*callback)(void)) {
    timer_t *timer = (timer_t*)malloc(sizeof(timer_t));
    if (timer == NULL) {
        perror("malloc");
        return NULL;
    }
    timer->id = id;
    timer->value = value;
    timer->callback = callback;
    timer->next = NULL;
    return timer;
}

此函数创建一个新的定时器,分配内存并初始化定时器的 idvaluecallback 等字段。

添加定时器到定时器组

void add_timer_to_group(timer_group_t *group, timer_t *timer) {
    if (group == NULL || timer == NULL) {
        return;
    }
    timer->next = group->head;
    group->head = timer;
    group->count++;
}

该函数将一个定时器添加到定时器组的链表头部,并更新定时器组的计数器。

删除定时器

void delete_timer(timer_group_t *group, unsigned int id) {
    if (group == NULL) {
        return;
    }
    timer_t *prev = NULL;
    timer_t *curr = group->head;

    while (curr != NULL && curr->id != id) {
        prev = curr;
        curr = curr->next;
    }

    if (curr == NULL) {
        return;
    }

    if (prev == NULL) {
        group->head = curr->next;
    } else {
        prev->next = curr->next;
    }

    free(curr);
    group->count--;
}

此函数在定时器组中查找并删除指定 id 的定时器。通过遍历链表找到目标定时器,然后调整链表指针并释放定时器的内存,同时更新定时器组的计数器。

启动定时器组

要启动定时器组,需要综合考虑所有定时器的定时值。一种简单的方法是找出所有定时器中最早触发的时间,并设置一个总的定时器。当这个总定时器触发时,检查每个定时器是否到期,如果到期则执行其回调函数。

#include <sys/time.h>
#include <signal.h>
#include <unistd.h>

void alarm_handler(int signum) {
    // 这里可以实现检查定时器组中各定时器是否到期并执行回调的逻辑
}

void start_timer_group(timer_group_t *group) {
    if (group == NULL) {
        return;
    }

    // 找到最早到期的定时器
    struct itimerval earliest;
    timer_t *curr = group->head;
    earliest = curr->value;
    curr = curr->next;
    while (curr != NULL) {
        if (curr->value.it_value.tv_sec < earliest.it_value.tv_sec ||
            (curr->value.it_value.tv_sec == earliest.it_value.tv_sec &&
             curr->value.it_value.tv_usec < earliest.it_value.tv_usec)) {
            earliest = curr->value;
        }
        curr = curr->next;
    }

    // 设置总定时器
    signal(SIGALRM, alarm_handler);
    struct itimerval new_value;
    new_value.it_value = earliest;
    new_value.it_interval.tv_sec = 0;
    new_value.it_interval.tv_usec = 0;
    setitimer(ITIMER_REAL, &new_value, NULL);
}

上述 start_timer_group 函数首先在定时器组中找到最早到期的定时器,并以此设置一个总定时器。当总定时器触发 SIGALRM 信号时,在 alarm_handler 函数中可以进一步检查每个定时器是否真正到期并执行相应的回调函数。

完整的定时器组管理示例代码

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

typedef struct timer {
    unsigned int id;
    struct itimerval value;
    void (*callback)(void);
    struct timer *next;
} timer_t;

typedef struct timer_group {
    timer_t *head;
    int count;
} timer_group_t;

timer_group_t* create_timer_group() {
    timer_group_t *group = (timer_group_t*)malloc(sizeof(timer_group_t));
    if (group == NULL) {
        perror("malloc");
        return NULL;
    }
    group->head = NULL;
    group->count = 0;
    return group;
}

timer_t* create_timer(unsigned int id, struct itimerval value, void (*callback)(void)) {
    timer_t *timer = (timer_t*)malloc(sizeof(timer_t));
    if (timer == NULL) {
        perror("malloc");
        return NULL;
    }
    timer->id = id;
    timer->value = value;
    timer->callback = callback;
    timer->next = NULL;
    return timer;
}

void add_timer_to_group(timer_group_t *group, timer_t *timer) {
    if (group == NULL || timer == NULL) {
        return;
    }
    timer->next = group->head;
    group->head = timer;
    group->count++;
}

void delete_timer(timer_group_t *group, unsigned int id) {
    if (group == NULL) {
        return;
    }
    timer_t *prev = NULL;
    timer_t *curr = group->head;

    while (curr != NULL && curr->id != id) {
        prev = curr;
        curr = curr->next;
    }

    if (curr == NULL) {
        return;
    }

    if (prev == NULL) {
        group->head = curr->next;
    } else {
        prev->next = curr->next;
    }

    free(curr);
    group->count--;
}

void alarm_handler(int signum) {
    // 实际应用中这里需要遍历定时器组,检查每个定时器是否到期并执行回调
    printf("Alarm signal received in group.\n");
}

void start_timer_group(timer_group_t *group) {
    if (group == NULL) {
        return;
    }

    struct itimerval earliest;
    timer_t *curr = group->head;
    earliest = curr->value;
    curr = curr->next;
    while (curr != NULL) {
        if (curr->value.it_value.tv_sec < earliest.it_value.tv_sec ||
            (curr->value.it_value.tv_sec == earliest.it_value.tv_sec &&
             curr->value.it_value.tv_usec < earliest.it_value.tv_usec)) {
            earliest = curr->value;
        }
        curr = curr->next;
    }

    signal(SIGALRM, alarm_handler);
    struct itimerval new_value;
    new_value.it_value = earliest;
    new_value.it_interval.tv_sec = 0;
    new_value.it_interval.tv_usec = 0;
    setitimer(ITIMER_REAL, &new_value, NULL);
}

// 示例回调函数
void timer1_callback() {
    printf("Timer 1 callback executed.\n");
}

void timer2_callback() {
    printf("Timer 2 callback executed.\n");
}

int main() {
    timer_group_t *group = create_timer_group();

    struct itimerval value1;
    value1.it_value.tv_sec = 2;
    value1.it_value.tv_usec = 0;
    value1.it_interval.tv_sec = 2;
    value1.it_interval.tv_usec = 0;
    timer_t *timer1 = create_timer(1, value1, timer1_callback);

    struct itimerval value2;
    value2.it_value.tv_sec = 3;
    value2.it_value.tv_usec = 0;
    value2.it_interval.tv_sec = 3;
    value2.it_interval.tv_usec = 0;
    timer_t *timer2 = create_timer(2, value2, timer2_callback);

    add_timer_to_group(group, timer1);
    add_timer_to_group(group, timer2);

    start_timer_group(group);

    while (1) {
        // 主循环防止程序退出
    }

    return 0;
}

在上述完整示例中,创建了一个定时器组,并向其中添加了两个定时器。每个定时器有不同的定时值和回调函数。start_timer_group 函数设置了一个基于最早到期定时器的总定时器。在实际应用中,alarm_handler 函数需要进一步完善,以准确检查每个定时器是否到期并执行相应的回调函数。

定时器组管理的优化

上述实现虽然基本满足了定时器组管理的需求,但在实际应用中还可以进一步优化。

定时器排序

在查找最早到期定时器时,当前的实现是通过遍历链表。如果定时器数量较多,这种方式效率较低。可以在添加定时器时对链表进行排序,使得定时器按到期时间从小到大排列。这样在查找最早到期定时器时,直接取链表头即可,时间复杂度从 O(n) 降低到 O(1)。

void add_timer_to_group_sorted(timer_group_t *group, timer_t *timer) {
    if (group == NULL || timer == NULL) {
        return;
    }
    timer_t *prev = NULL;
    timer_t *curr = group->head;

    while (curr != NULL && (curr->value.it_value.tv_sec < timer->value.it_value.tv_sec ||
                           (curr->value.it_value.tv_sec == timer->value.it_value.tv_sec &&
                            curr->value.it_value.tv_usec < timer->value.it_value.tv_usec))) {
        prev = curr;
        curr = curr->next;
    }

    if (prev == NULL) {
        timer->next = group->head;
        group->head = timer;
    } else {
        prev->next = timer;
        timer->next = curr;
    }
    group->count++;
}

高精度定时器

对于一些对时间精度要求较高的应用场景,setitimer 的精度可能不够。可以考虑使用 timerfd_create 函数创建高精度定时器。timerfd_create 函数创建一个文件描述符,当定时器到期时,该文件描述符可读,通过 read 操作可以获取定时器到期的次数。

#include <sys/timerfd.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/time.h>

int main() {
    int timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
    if (timerfd == -1) {
        perror("timerfd_create");
        return 1;
    }

    struct itimerspec new_value;
    new_value.it_value.tv_sec = 2;
    new_value.it_value.tv_nsec = 0;
    new_value.it_interval.tv_sec = 1;
    new_value.it_interval.tv_nsec = 0;

    if (timerfd_settime(timerfd, 0, &new_value, NULL) == -1) {
        perror("timerfd_settime");
        close(timerfd);
        return 1;
    }

    uint64_t exp;
    ssize_t s = read(timerfd, &exp, sizeof(uint64_t));
    if (s != sizeof(uint64_t)) {
        perror("read");
    } else {
        printf("Timer expired %llu times.\n", (unsigned long long)exp);
    }

    close(timerfd);
    return 0;
}

在定时器组管理中引入 timerfd_create 时,需要对定时器组的数据结构和操作函数进行相应的修改,以适应这种新的定时器机制。

多线程环境下的定时器组管理

在多线程程序中使用定时器组时,需要考虑线程安全问题。由于多个线程可能同时访问和修改定时器组的数据结构,可能会导致数据竞争和不一致。

互斥锁保护

可以使用互斥锁(pthread_mutex_t)来保护定时器组的关键操作,如添加、删除和启动定时器等。

#include <pthread.h>

typedef struct timer_group {
    timer_t *head;
    int count;
    pthread_mutex_t mutex;
} timer_group_t;

timer_group_t* create_timer_group() {
    timer_group_t *group = (timer_group_t*)malloc(sizeof(timer_group_t));
    if (group == NULL) {
        perror("malloc");
        return NULL;
    }
    group->head = NULL;
    group->count = 0;
    if (pthread_mutex_init(&group->mutex, NULL) != 0) {
        perror("pthread_mutex_init");
        free(group);
        return NULL;
    }
    return group;
}

void add_timer_to_group(timer_group_t *group, timer_t *timer) {
    if (group == NULL || timer == NULL) {
        return;
    }
    pthread_mutex_lock(&group->mutex);
    timer->next = group->head;
    group->head = timer;
    group->count++;
    pthread_mutex_unlock(&group->mutex);
}

void delete_timer(timer_group_t *group, unsigned int id) {
    if (group == NULL) {
        return;
    }
    pthread_mutex_lock(&group->mutex);
    timer_t *prev = NULL;
    timer_t *curr = group->head;

    while (curr != NULL && curr->id != id) {
        prev = curr;
        curr = curr->next;
    }

    if (curr == NULL) {
        pthread_mutex_unlock(&group->mutex);
        return;
    }

    if (prev == NULL) {
        group->head = curr->next;
    } else {
        prev->next = curr->next;
    }

    free(curr);
    group->count--;
    pthread_mutex_unlock(&group->mutex);
}

通过在关键操作前后加锁和解锁,确保在同一时间只有一个线程能够修改定时器组的数据结构,从而保证线程安全。

条件变量

在启动定时器组时,可能需要等待所有定时器都准备好后再开始。这时可以使用条件变量(pthread_cond_t)。

#include <pthread.h>

typedef struct timer_group {
    timer_t *head;
    int count;
    pthread_mutex_t mutex;
    pthread_cond_t cond;
    int all_ready;
} timer_group_t;

timer_group_t* create_timer_group() {
    timer_group_t *group = (timer_group_t*)malloc(sizeof(timer_group_t));
    if (group == NULL) {
        perror("malloc");
        return NULL;
    }
    group->head = NULL;
    group->count = 0;
    if (pthread_mutex_init(&group->mutex, NULL) != 0) {
        perror("pthread_mutex_init");
        free(group);
        return NULL;
    }
    if (pthread_cond_init(&group->cond, NULL) != 0) {
        perror("pthread_cond_init");
        pthread_mutex_destroy(&group->mutex);
        free(group);
        return NULL;
    }
    group->all_ready = 0;
    return group;
}

void add_timer_to_group(timer_group_t *group, timer_t *timer) {
    if (group == NULL || timer == NULL) {
        return;
    }
    pthread_mutex_lock(&group->mutex);
    timer->next = group->head;
    group->head = timer;
    group->count++;
    if (group->count == expected_count) {
        group->all_ready = 1;
        pthread_cond_signal(&group->cond);
    }
    pthread_mutex_unlock(&group->mutex);
}

void start_timer_group(timer_group_t *group) {
    pthread_mutex_lock(&group->mutex);
    while (!group->all_ready) {
        pthread_cond_wait(&group->cond, &group->mutex);
    }
    pthread_mutex_unlock(&group->mutex);

    // 启动定时器组的逻辑
}

在上述代码中,add_timer_to_group 函数在添加定时器后检查是否所有定时器都已添加,如果是则设置 all_ready 并发送条件变量信号。start_timer_group 函数在启动定时器组前等待条件变量信号,确保所有定时器都准备好。

总结定时器组管理要点

在 Linux C 语言编程中,定时器组管理是一项重要的技术,它能够有效地组织和管理多个定时器,提高程序的可维护性和效率。通过合理设计数据结构和实现相关操作函数,结合优化策略和线程安全机制,可以满足各种复杂的定时需求。无论是简单的周期性任务,还是对时间精度和多线程环境有要求的应用场景,都可以通过精心设计的定时器组管理方案来实现。在实际应用中,需要根据具体的需求和场景,选择合适的定时器机制和优化策略,以确保程序的稳定性和高效性。同时,要注意处理好定时器组管理中的各种边界情况,如定时器的创建失败、删除不存在的定时器等,使程序具有更好的健壮性。