Linux C定时器,如何实现精确延时与周期任务?

99ANYc3cd6
预计阅读时长 15 分钟
位置: 首页 C语言 正文

sleep / usleep (最简单,但不精确)

这是最简单、最直接的定时方法,适用于对精度要求不高的场景。

linux c语言 定时器
(图片来源网络,侵删)
  • sleep(unsigned int seconds): 暂停执行指定的秒数。
  • int usleep(useconds_t usec): 暂停执行指定的微秒数 (1秒 = 1,000,000微秒)。

特点:

  • 优点: 非常简单,易于使用。
  • 缺点:
    • 不精确: sleep 会阻塞整个进程,直到时间结束,如果进程在 sleep 期间被信号中断,它会提前返回。usleep 也是阻塞式的,并且在现代 Linux 系统中已被标记为过时(deprecated)。
    • 功能单一: 只能实现一次性延迟,不能周期性执行。

示例代码:

#include <stdio.h>
#include <unistd.h> // for sleep()
int main() {
    printf("程序开始,准备休眠5秒...\n");
    sleep(5); // 阻塞5秒
    printf("5秒已过,程序继续运行,\n");
    printf("准备休眠100毫秒 (100,000微秒)...\n");
    // 注意: usleep在较新的系统上可能不可用,且不推荐使用
    usleep(100000); 
    printf("100毫秒已过,\n");
    return 0;
}

alarm + signal (信号驱动,一次性)

这种方法使用信号机制,它允许你设置一个定时器,当时间到达时,内核会向你的进程发送一个 SIGALRM 信号,你需要定义一个信号处理函数来响应这个信号。

特点:

linux c语言 定时器
(图片来源网络,侵删)
  • 优点: 相比 sleep,这是一种非阻塞式的(在信号处理函数执行前),可以让你在等待定时器期间执行其他任务。
  • 缺点:
    • 一次性: alarm 只能设置一次,每次只能有一个 SIGALRM 在等待,你需要重新调用 alarm 来实现周期性。
    • 全局状态: 信号处理函数的设计有诸多限制(在信号处理函数中应尽量只调用异步信号安全的函数,如 write, _exit,避免使用 printf, malloc 等),容易引入 bug。

示例代码:

#include <stdio.h>
#include <signal.h> // for signal(), SIGALRM
#include <unistd.h> // for alarm()
// 信号处理函数
void alarm_handler(int signum) {
    printf("收到 SIGALRM 信号!定时器时间到,\n");
}
int main() {
    // 注册 SIGALRM 信号的处理器
    signal(SIGALRM, alarm_handler);
    printf("程序开始,设置一个3秒的定时器,\n");
    alarm(3); // 3秒后发送 SIGALRM 信号
    // 主线程可以在这里做其他事情,而不是被阻塞
    // 我们用一个简单的循环来模拟
    while(1) {
        printf("主线程正在运行...\n");
        sleep(1);
    }
    // alarm(0); // 可以取消之前的定时器
    return 0;
}

setitimer (功能强大,推荐)

这是最常用、最灵活、最可靠的定时器方法,是 alarm 的超集,它可以设置三种不同的定时器:

  1. ITIMER_REAL: 以真实时间计时,时间到发送 SIGALRM 信号。
  2. ITIMER_VIRTUAL: 以进程在用户态下运行的时间计时,时间到发送 SIGVTALRM 信号。
  3. ITIMER_PROF: 以进程在用户态和内核态下运行的总时间计时,时间到发送 SIGPROF 信号。

setitimer 可以实现一次性周期性定时,并且可以获取剩余时间。

特点:

linux c语言 定时器
(图片来源网络,侵删)
  • 优点:
    • 高精度: 比前两种方法更精确。
    • 功能强大: 支持一次性、周期性定时。
    • 灵活性: 提供三种不同的时间基准。
    • 非阻塞: 和 alarm 一样,通过信号机制工作,不会阻塞主线程。
  • 缺点:
    • 同样需要处理信号,有信号处理函数的限制。
    • API 比 sleep 复杂一些。

数据结构: struct itimerval 用于定义定时器的参数:

struct itimerval {
    struct timeval it_interval; // 定时器周期性重复的时间间隔
    struct timeval it_value;   // 第一次触发的时间倒计时
};
struct timeval {
    long tv_sec;  // 秒
    long tv_usec; // 微秒
};

示例代码 (周期性定时器): 这个例子演示了如何每2秒打印一次 "定时器触发!",同时主线程不受影响,持续打印自己的信息。

#include <stdio.h>
#include <signal.h>   // for signal(), SIGALRM
#include <unistd.h>   // for getpid()
#include <sys/time.h> // for setitimer(), struct itimerval
// 全局变量,用于计数
int timer_count = 0;
// 信号处理函数
void timer_handler(int signum) {
    timer_count++;
    printf("定时器触发!这是第 %d 次,\n", timer_count);
}
int main() {
    // 注册 SIGALRM 信号的处理器
    signal(SIGALRM, timer_handler);
    // 初始化 itimerval 结构体
    struct itimerval timer;
    // 设置第一次触发的时间为2秒
    timer.it_value.tv_sec = 2;
    timer.it_value.tv_usec = 0;
    // 设置之后每次重复的时间间隔为2秒 (周期性)
    timer.it_interval.tv_sec = 2;
    timer.it_interval.tv_usec = 0;
    // 启动定时器
    // ITIMER_REAL: 使用真实时间,触发 SIGALRM
    if (setitimer(ITIMER_REAL, &timer, NULL) == -1) {
        perror("setitimer");
        return 1;
    }
    printf("定时器已启动,进程ID: %d\n", getpid());
    printf("主线程开始运行...\n");
    // 主线程可以执行其他任务,不会被定时器阻塞
    while(1) {
        sleep(1); // 模拟主线程工作
        printf("主线程正在运行...\n");
    }
    return 0;
}

总结与对比

方法 阻塞方式 精度 周期性 复杂度 适用场景
sleep/usleep 阻塞 非常低 简单的延时任务,对精度要求不高。
alarm+signal 非阻塞 (信号驱动) 否 (需手动重置) 一次性任务,需要响应事件,且精度要求不高。
setitimer 非阻塞 (信号驱动) 强烈推荐,需要高精度、周期性、一次性定时的各种复杂场景。

如何选择?

  • 如果你只是想让程序“等一下”,并且不关心等待期间是否还能做别的事,用 sleep 最简单。
  • 如果你需要响应一个一次性的事件,并且希望程序在等待时能做点别的事,可以用 alarm
  • 如果你需要高精度的定时,或者需要周期性地执行某个任务,毫无疑问,应该使用 setitimer,它是 Linux 下功能最全面的定时器解决方案。

对于大多数需要定时器的应用场景,setitimer 是最佳选择。

-- 展开阅读全文 --
头像
织梦手机端页面为何不生成?
« 上一篇 前天
装修公司为何用织梦建网站?
下一篇 » 前天

相关文章

取消
微信二维码
支付宝二维码

目录[+]