Linux time function in C/C++【2】

Linux 时间与定时机制设计思想的核心


一、时间函数的分类与作用

Linux 的时间函数大体分为两类:

(1)计时函数(获取当前时间)

这些函数用于读取当前的系统时间或高精度时间戳,区别在于时间单位与精度不同:

函数 返回结构 精度 说明
time() time_t(整型秒) 秒级 最简单、最老的API
ftime() struct timeb 毫秒级 已被废弃
gettimeofday() struct timeval 微秒级 常用,用户态实现,高效
clock_gettime() struct timespec 纳秒级 最精确,但调用代价略高

(2)定时函数(控制程序等待或定时触发)

这些函数用于让程序等待一定时间或设置定时器,常用于超时控制、周期任务等:

函数 类型 是否线程安全 是否基于信号 是否可与 I/O 复用机制结合
sleep() 秒级休眠 是(SIGALRM)
usleep() 微秒级休眠 是(SIGALRM)
nanosleep() 纳秒级休眠
alarm() 设置闹钟信号 是(SIGALRM)
getitimer() / setitimer() 定时器信号
timer_create() / timer_settime() POSIX 定时器 可选信号
timerfd_create() / timerfd_settime() 文件描述符定时器 可与 epoll/poll 集成

timerfd_* 系列,能直接与 Reactor 模型兼容。


二、为何选择 gettimeofday() 而不是 clock_gettime()

原因 1:精度与开销平衡

  • time() 只能到秒,不够;
  • ftime() 已被淘汰;
  • clock_gettime() 虽能到纳秒,但调用属于系统调用,会陷入内核,开销较高
  • gettimeofday() 精度达微秒,且 在 x86-64 平台是纯用户态实现(无系统调用陷入),因此速度极快。

也就是说:
clock_gettime() 追求极致精度;
gettimeofday() 追求低开销和足够的精度(微秒级即可满足绝大多数需求)。


原因 2:实现机制优越

在 x86-64 平台上,gettimeofday() 的实现利用了 VDSO(Virtual Dynamic Shared Object)机制,让用户态程序可直接访问内核的共享时间数据结构,无需切换上下文。



三、为何选择 timerfd_* 而非其他定时函数

原因 1:避免信号混乱

  • sleep()usleep()alarm()getitimer()setitimer() 等函数都依赖 SIGALRM 信号

  • 多线程程序中,信号机制非常危险:

    • 不确定信号由哪个线程处理;
    • 信号处理函数受限(不可调用大部分系统API);
    • 若主程序与第三方库都用 SIGALRM,极易发生冲突。

强调:多线程程序应尽量避免信号驱动定时。

  • 不确定信号由哪个线程处理:

    • 由于信号处理是异步且不可预测的,操作系统可能将信号随机分配给任意线程
    • 主线程和工作线程都可能成为信号处理者,导致程序行为不可控
    • 例如在Web服务器中,SIGTERM信号可能被处理请求的工作线程捕获,而非主线程
    • POSIX标准规定信号的处理线程是未指定的,不同实现表现各异
  • 信号处理会中断线程的正常执行流:

    • 信号处理函数可能在任何时刻抢占线程执行
    • 若信号发生在临界区内,可能破坏共享数据的一致性
    • 比如数据库写入操作被信号中断,导致数据损坏
  • 线程安全信号处理难度大:

    • 信号处理函数中只能使用异步信号安全函数
    • 常规的锁机制在信号处理中不可用
    • 需要复杂的线程间协调机制来保证安全
  • 信号处理与线程取消的交互问题:

    • 信号可能意外触发线程取消点
    • 导致资源清理和状态恢复更加困难
    • 在实时系统中这类问题尤为突出

原因 2:非阻塞编程要求"事件驱动"

  • nanosleep() 是线程安全的,但会让线程挂起
  • 会造成事件循环停滞
  • 正确做法:用定时器注册回调函数,让事件循环继续执行。

原因 3:timerfd 与 epoll完美结合

  • timerfd_create() 创建的定时器表现为一个文件描述符(fd)
  • 当定时到期,该 fd 就会"变得可读";
  • 因此它能直接与 select()poll()epoll() 结合;
  • 统一处理网络I/O事件与定时事件,形成优雅一致的事件循环机制

原因 4:精度更高

  • epoll_wait()poll() 的 timeout 参数是毫秒级;
  • timerfd_settime() 可达 纳秒级 精度。

因此,timerfd 提供了高精度、统一接口、线程安全的定时解决方案。


四、Linux 定时与计时的现实限制

在非实时 Linux 系统中,不可能做到"绝对精确"的计时。

原因是:

  • Linux 是非实时调度系统
  • 任意时刻,内核可能把当前任务换出;
  • 尤其在 CPU 负载高时,误差更明显。

因此我们能做的只是提高"时间精度的可靠性"

比如:

  • 控制 CPU 负载;
  • 使用合适的优先级;
  • 利用高精度 timerfd;
  • 保证程序在"99.99% 的情况下"按预期执行。

五、精度 vs 分辨率

最后两者区别:

  • Resolution(分辨率):时间函数能"分出多细的时间单位",比如微秒或纳秒;
  • Accuracy(准确度):返回值与真实时间的偏差;

举例:

你可能有一个 1 微秒分辨率的时钟,但在高负载时偏差达 100 微秒,这意味着分辨率高但准确度低。


六、总结对比表

功能类别 典型函数 精度 是否阻塞线程 是否依赖信号 是否线程安全 是否适合Reactor
计时 gettimeofday() 微秒
定时 sleep()
定时 nanosleep() 纳秒
定时 getitimer() 微秒
定时 timer_create() 纳秒 ✅可控
定时 timerfd_create() 纳秒

七、核心理念总结

  • 获取时间:gettimeofday() ------ 快、够精、用户态;
  • 定时任务:timerfd_* ------ 精度高、无信号干扰、易于与 epoll 集成;
  • Reactor 统一事件模型:IO 与定时都用"fd"驱动;
  • 非实时系统中追求高可靠时间精度而非"绝对精确"。

相关推荐
小武~3 小时前
嵌入式网络编程深度优化 --网络协议栈配置实战指南
linux·网络·网络协议
二进制星轨3 小时前
在 Ubuntu 上快速配置 Node.js 环境(附问题说明)
linux·ubuntu·node.js
一个不知名程序员www3 小时前
算法学习入门---前缀和(C++)
c++·算法
kblj55553 小时前
学习Linux——网络——网卡
linux·网络·学习
Physicist in Geophy.3 小时前
新版ubuntu中sac安装问题(缺少libncurses5)
linux·运维·ubuntu
yuuki2332333 小时前
【数据结构】双向链表的实现
c语言·数据结构·后端
菲兹园长3 小时前
微服务组件(E、L、N、O、G)
linux·服务器·gateway
ol木子李lo3 小时前
Doxygen入门指南:从注释到自动文档
c语言·c++·windows·编辑器·visual studio code·visual studio·doxygen
夜晚中的人海4 小时前
【C++】分治-快速排序算法习题
开发语言·c++·排序算法