不要混用 time、gettimeofday 和 clock_gettime(CLOCK_REALTIME)
0. TLDR
Linux 下 time
、gettimeofday
、clock_gettime(CLOCK_REALTIME)
三个接口返回的时间不是严格同步的,time
相比其他两个有毫秒级的延迟更新。即精确系统时间从 t 秒跳到 (t + 1) 秒后,gettimeofday
和 clock_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_REALTIME
和 gettimeofday
精确同步(显示出来的值有细微差异是调用顺序的原因);而 time
比它们后调用,返回的时间却更早,可以确定是系统对它们的更新不同步。
所以代码需避免混用这三个调用,否则可能出现意外的「时间倒流」。
4. 为什么会不同步
查了内核代码,大概理解了原因:
1. 这三个函数的实现原理都一样:内核不断向一块共享内存写入当前时间,用户态 libc 通过 vDSO 读取共享内存,不用进内核
2. 内核每 4ms 写一次共享内容,写入高精度当前时间(basetime)以及当前 CPU counter
3. gettimeofday
和 clock_gettime(CLOCK_REALTIME)
会读取 basetime,并根据 CPU counter 的差值添加一个校正项,即加上内核上次更新共享内存后到现在过去的时间
4. time
直接读取 basetime 返回,不根据 CPU counter 校正,所以会表现为延迟更新,最大延迟 4ms
5. COARSE 类时钟也不校正,所以 time
和 CLOCK_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。