目录
  • 墙上时钟与单调时钟
    • 墙上时钟
    • 单调时钟
    • Time的结构
    • Since的实现
  • 小结
    • 参考资料

      墙上时钟与单调时钟

      墙上时钟

      墙上时钟也称为墙上时间。大多是1970年1月1日(UTC)以来的秒数和毫秒数。

      墙上时间可以和NTP(Network Time Protocal,网络时间协议)同步,但是如果本地时钟远远快于NTP服务器,则强制重置之后会跳到先前某个时间点。(这里不是很确定,猜测是如果时间差的不多,则调整石英晶体振荡器的频率,慢慢一致。如果差很多,则强行一致)

      单调时钟

      机器大多有自己的石英晶体振荡器,并将其作为计时器。单调时钟的绝对值没有任何意义,根据操作系统和语言的不同,单调时钟可能在程序开始时设为0、或在计算机启动后设为0等等。但是通过比较同一台计算机上两次单调时钟的差,可以获得相对准确的时间间隔。

      Time的结构

       type Time struct {
          // wall and ext encode the wall time seconds, wall time nanoseconds,
          // and optional monotonic clock reading in nanoseconds.
          //
          // From high to low bit position, wall encodes a 1-bit flag (hasMonotonic),
          // a 33-bit seconds field, and a 30-bit wall time nanoseconds field.
          // The nanoseconds field is in the range [0, 999999999].
          // If the hasMonotonic bit is 0, then the 33-bit field must be zero
          // and the full signed 64-bit wall seconds since Jan 1 year 1 is stored in ext.
          // If the hasMonotonic bit is 1, then the 33-bit field holds a 33-bit
          // unsigned wall seconds since Jan 1 year 1885, and ext holds a
          // signed 64-bit monotonic clock reading, nanoseconds since process start.
          wall uint64
          ext  int64
          ...
       }
      复制代码

      wall和ext共同记录了时间,但是分为两种情况,一种是没有记录单调时钟(比如是通过字符串解析得到的时间),另一种是记录了单调时钟(比如通过Now)。

      wall的第一位是一个标记位

      如果为1,则表示记录了单调时钟。则wall的2-34(闭区间)位记录了从1885-1-1到现在的秒数,最后30位记录了纳秒数。而ext记录了从程序开始运行到现在经过的单调时钟数。

      如果为0,则表示没有记录单调时钟。则wall的2-34(闭区间)位全部为0(那最后30位是啥?)。而ext记录了从1-1-1到现在经过的秒数。

      Since的实现

      golang 墙上时钟与单调时钟的实现

      这里比较关键的代码是第914行的runtimeNano() - startNanostartNano的含义还是直接上代码比较明了。

       var startNano = 0
       ​
       func init(){
           startNano = runtimeNano()
       }

      runtimeNano()是调用了汇编,获取了操作系统当前的单调时钟。前面说过,单调时钟的绝对值没有什么意义。因此这里将两个时间相减,得到了从程序开始到现在的单调时钟。

      然后看一下Sub

       func (t Time) Sub(u Time) Duration {
          if t.wall&u.wall&hasMonotonic != 0 {
             te := t.ext
             ue := u.ext
             d := Duration(te - ue)
             if d < 0 && te > ue {
                return maxDuration // t - u is positive out of range
             }
             if d > 0 && te < ue {
                return minDuration // t - u is negative out of range
             }
             return d
          }
          d := Duration(t.sec()-u.sec())*Second + Duration(t.nsec()-u.nsec())
          // Check for overflow or underflow.
          switch {
          case u.Add(d).Equal(t):
             return d // d is correct
          case t.Before(u):
             return minDuration // t - u is negative out of range
          default:
             return maxDuration // t - u is positive out of range
          }
       }

      这里我们只需要关注2-13行即可。除去了范围检查,这里的主要逻辑就是两个Time的ext相减。而ext又都代表了单调时钟,所以最后返回的是单调时钟的差值。

      小结

      在分布式系统中,我们经常需要判断时间间隔来检测心跳。而墙上时钟与NTP的组合可能会带来时间的前后跳跃与闪烁,所以使用单调时钟更加安全和保险。

      在go语言中,没有直接调用调用时钟的函数。可以通过time.Now()获得带单调时钟的Time结构体,并通过Since和Until获得相对准确的时间间隔。

      参考资料

      go time分析

      一个commit

      go1.14.2 源码

      数据密集型应用系统设计(书)

      声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。