我能否指望clock_gettime(CLOCK_MONOTONIC) 为我提供单调时间戳,即使在不同内核上运行的不同线程中也是如此?
时间戳仅在同一核心上保证单调。也就是说,如果你有
Thread on CPU A core C Thread on CPU B core D
pthread_mutex_lock(&lock);
T1 = clock_gettime(CLOCK_MONOTONIC);
pthread_mutex_unlock(&lock); pthread_mutex_lock(&lock);
T2 = clock_gettime(CLOCK_MONOTONIC);
pthread_mutex_unlock(&lock);
没有绝对保证T2 > T1。
Linux 内核尽最大努力确保T2 > T1,但问题在于硬件:一些硬件没有足够好的同步时间源。在这样的硬件上,创建一个在所有 CPU 和内核之间保持同步的可靠单调时钟将需要进程间中断或其他方式来将单个时钟值保持在某个地方,这太慢而无法高效。
在某些配置中,已知时钟源在所有 CPU 内核之间同步。例如,如果/proc/cpuinfo 中的所有physical ID: 字段都相同,并且所有flags: 字段都具有tsc_reliable,则已知时间戳计数器寄存器在所有内核之间同步并用作时间源。但是,在实践中,您不会进行此类检查,因为结果是推断的,内核无法保证,因此可能是错误的。
在实践中,我们计算事物时就好像我们假设 CLOCK_MONOTONIC 在内核之间是单调的,但是是务实的和检查的。
对于可能不同内核上的线程之间的时间消息传递或信号,我们测量往返时间。使用大量的往返,并选择时间的中位数:这会给你一个可靠的结果,你可以说“至少有一半的往返在时间 T 内完成" 充满信心,毫不含糊。 (通常你可能会选择一个更高的点,比如 68.3% 或 95%。)
如果您需要可靠的 CLOCK_MONOTONIC 派生时间戳跨可以访问同一共享内存段的进程,您可以通过将“当前”时间戳存储在该共享内存中来实现它。
当一个进程想要一个时间戳时,它相当于
Do:
T0 = clock_gettime(CLOCK_MONOTONIC)
Ts = shared timestamp
T = max(T0, Ts)
While CompareExchange(shared timestamp, Ts, T) fails.
Use T as timestamp.
也就是说,它比较本地单调时钟和共享时间戳,将共享时间戳更新为两者中较高的一个,并将其用作时间戳。
您可以使用 GCC 的 __atomic_compare_exchange_n() 内置更新共享时间戳,而无需持有任何锁。 (不必从共享内存中原子地读取时间戳,因为原子性比较和交换会处理原子性。)
唯一的缺点是,如果很多线程经常这样做,由于缓存线乒乓,你确实会得到一些开销。
请注意,如果您使用uint64_t(以纳秒为单位)作为时间戳,您将需要在max 函数中考虑回绕:
static inline uint64_t max_wraparound(const uint64_t a, const uint64_t b)
{
return ((uint64_t)(a - b) < UINT64_C(9223372036854775808)) ? a : b;
}
这样,任何两个此类时间戳(before 和 after)之间的差异始终为 (uint64_t)(after - before),即使时间戳值介于两者之间。