Go中使用单调时钟获得准确的时间间隔问题
目录
- 墙上时钟与单调时钟
- 墙上时钟
- 单调时钟
- 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的实现
这里比较关键的代码是第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 源码
- 数据密集型应用系统设计(书)
到此这篇关于Go中使用单调时钟获得准确的时间间隔的文章就介绍到这了,更多相关go时间间隔内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!