不要混用 time、gettimeofday 和 clock_gettime(CLOCK_REALTIME)

2025-05-01

0. TLDR

Linux 下 timegettimeofdayclock_gettime(CLOCK_REALTIME) 三个接口返回的时间不是严格同步的,time 相比其他两个有毫秒级的延迟更新。即精确系统时间从 t 秒跳到 (t + 1) 秒后,gettimeofdayclock_gettime(CLOCK_REALTIME) 会立即开始返回 tv_sec = t + 1,而 time 还会持续返回 t 一小段时间(最多 4ms)。

1. 背景

获取系统时间,精度从低到高有三个接口:time(秒)、gettimeofday(微秒)、clock_gettime(CLOCK_REALTIME)(精度取决于系统实现,最高可到纳秒)

2. 现象

场景是这样:希望代码在 00:00:00 准时运行,使用 clock_nanosleep 睡眠到 00:00:00,然而运行时有不小概率出现唤醒后 time(NULL) 返回的时间还是前一天的 23:59:59。

3. 排查

本以为是 clock_nanosleep 提前唤醒了,但后来发现把 time(NULL) 改成 clock_gettime(CLOCK_REALTIME, &t) 就不会出现 23:59:59。于是怀疑系统时钟有坑。

写代码进一步验证:

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

int main() {
  for (;;) {
    struct timespec ts;
    struct timeval tv;
    clock_gettime(CLOCK_REALTIME, &ts);
    gettimeofday(&tv, NULL);
    time_t t = time(0);
    if (ts.tv_sec != t || tv.tv_sec != t) {
      printf("CLOCK_REALTIME %lu.%09lu gettimeofday %lu.%06lu time %lu\n",
             ts.tv_sec, ts.tv_nsec, tv.tv_sec, tv.tv_usec, t);
    }
  }
  return 0;
}

运行结果:

CLOCK_REALTIME 1745918737.000000032 gettimeofday 1745918737.000000 time 1745918736
CLOCK_REALTIME 1745918737.000016443 gettimeofday 1745918737.000016 time 1745918736
CLOCK_REALTIME 1745918737.000018226 gettimeofday 1745918737.000018 time 1745918736
.....
CLOCK_REALTIME 1745918817.000898650 gettimeofday 1745918817.000898 time 1745918816
CLOCK_REALTIME 1745918817.000899031 gettimeofday 1745918817.000899 time 1745918816
CLOCK_REALTIME 1745918817.000899411 gettimeofday 1745918817.000899 time 1745918816
CLOCK_REALTIME 1745918817.000899792 gettimeofday 1745918817.000899 time 1745918816
CLOCK_REALTIME 1745918817.999999976 gettimeofday 1745918818.000000 time 1745918817
CLOCK_REALTIME 1745918818.000005406 gettimeofday 1745918818.000005 time 1745918817
CLOCK_REALTIME 1745918818.000006007 gettimeofday 1745918818.000006 time 1745918817
CLOCK_REALTIME 1745918818.000006418 gettimeofday 1745918818.000006 time 1745918817
...

可以看到:

(1) 这台机上 time 相比 CLOCK_REALTIME / gettimeofday 的延迟约 0.9ms。

(2) CLOCK_REALTIMEgettimeofday 精确同步(显示出来的值有细微差异是调用顺序的原因);而 time 比它们后调用,返回的时间却更早,可以确定是系统对它们的更新不同步。

所以代码需避免混用这三个调用,否则可能出现意外的「时间倒流」。

4. 为什么会不同步

查了内核代码,大概理解了原因:

1. 这三个函数的实现原理都一样:内核不断向一块共享内存写入当前时间,用户态 libc 通过 vDSO 读取共享内存,不用进内核

2. 内核每 4ms 写一次共享内容,写入高精度当前时间(basetime)以及当前 CPU counter

3. gettimeofdayclock_gettime(CLOCK_REALTIME) 会读取 basetime,并根据 CPU counter 的差值添加一个校正项,即加上内核上次更新共享内存后到现在过去的时间

4. time 直接读取 basetime 返回,不根据 CPU counter 校正,所以会表现为延迟更新,最大延迟 4ms

5. COARSE 类时钟也不校正,所以 timeCLOCK_REALTIME_COARSE 的更新是同步的,程序验证也可以确认这一点

5. 附注:macOS

macOS 下这三个接口是严格同步的,time(NULL) 的返回值始终等于另外两个接口返回的 tv_sec

但 macOS 的 CLOCK_REALTIME 只提供了微秒精度,精度和 gettimeofday 相同。

6. 附注:其他时间接口

目前已发现 Linux 下文件系统使用的时间也有延迟。例如,在 clock_gettime(CLOCK_REALTIME) 已返回 00:00:00 时,创建的文件的时间戳仍然可能是 23:59:59。