Go中使用单调时钟获得准确的时间间隔
墙上时钟与单调时钟
墙上时钟
墙上时钟也称为墙上时间。大多是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的实现
img这里比较关键的代码是第914行的runtimeNano() - startNano
。startNano
的含义还是直接上代码比较明了。
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分析:https://zhuanlan.zhihu.com/p/93299132)
- 一个commit:https://link.zhihu.com/?target=https%3A//github.com/golang/go/commit/fc3f8d43f1b7da3ee3fb9a5181f2a86841620273
- go1.14.2 源码
- 数据密集型应用系统设计(书)
如果你觉得文章不错
可以扫码关注
我们的公众号哦~
带你阅读更多优秀文章~
分享-点赞-在看
可三连戳~