clock_gettime(CLOCK_MONOTONIC)跨核心/线程的单调性

时间:2017-10-23 15:27:40

标签: c linux multithreading clock

我有多个进程可以在双处理器X86-64 Linux机器的不同内核上相互通信。通信内容包括时间戳。我想用简单的假设来编写程序的时间相关逻辑,所有时间戳都来自同一个全局时钟。我是否可以依靠$token = $this->tokenStorage->getToken(); if ($token) { $user = $token->getUser(); } // $user->getCanonicalUsername() -- logged in username // can also check what permissions a user has if ($this->security->isGranted('ROLE_ADMIN')) { $menu->addChild('menu.admin', ['route' => 'admin_dashboard']); } 为我提供单调时间戳,即使在不同核心上运行的不同线程上也是如此?

特别是,假设进程A采用时间戳X并通过共享内存将其发送到进程B.进程B读取它然后采用时间戳Y.X不能大于Y.

使用clock_gettime(CLOCK_MONOTONIC)获取的时间戳是否具有上述属性?如果没有,那么具有此属性的其他类型的单调时间戳是什么?

2 个答案:

答案 0 :(得分:1)

  

我可以依靠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内核之间都是同步的。例如,如果physical ID:中的所有/proc/cpuinfo字段都相同,并且所有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()来更新共享时间戳,而无需持有任何锁。 (不必从共享内存中原子地读取时间戳,因为原子比较和交换会照顾原子性。)

唯一的缺点是,如果许多线程经常执行此操作,则由于高速缓存行ping-pong,确实会产生一些开销。

请注意,如果您使用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;
}

这样,即使时间戳值介于两者之间,beforeafter这两个时间戳之间的差异始终为(uint64_t)(after - before)

答案 1 :(得分:1)

POSIX根据系统范围的时钟定义CLOCK_MONOTONIC。全系统范围是指符合单个系统映像的所有核心,套接字,集群。一个内核]。

我很感兴趣地阅读了Nominal Animals的答案,这让我感到有些震惊,因为可以看到CLOCK_MONOTONIC向后移动并似乎违反了POSIX合同。