Go 标准库 time 包之 monotonic clocks
继续上一章节未完成的介绍,本章节我们主要讲解下 monotonic clock ( 单调时钟 ) 在 time
的运用,包括但不限于如何使用 单调时间 来测量两个时间的间隔
事先声明下,本章节的内容是关于 time 包中的方法如何使用 monotonic clock 的细节,但这些细节并不是使用 time
包所必须的。
Time
结构包含了 monotonic clocks
这是第一个认知,也是最重要的认知。Time
对象不仅包含了 1970 年 1 月 1 日 0 时 0 分 0 秒以来的时间戳,还包括了系统启动以来的 monotonic clock。
time.Now
这个方法返回的 Time
结构对象就包含了 monotonic clock 。( 关于 Time
结构的更多知识,可以访问我们的 Go 标准库 time 包之 Time 结构 )
如果一个 Time
结构对象 t
包含了 monotonic clock 读数 ( reading ) ,那么 t.Add()
方法会将相同的持续时间间隔 ( during time ) 同时添加到 wall time 和 monotonic clock 的读数上以计算结果。
你可能会问,t.AddDate(y, m, d)
、t.Round(d)
和 t.Truncate(d)
方法不是根据 wall clock 来计算的吗 ?
是的,它们的确是根据 wall clock 来计算的,只不过它们在进行计算的时候会把 monotonic clock 读数给去除掉,只计算 wall clock
另一方面,你可能也直到,t.In
、t.Local
和 t.UTC
只是用于解释 wall clock 时间,因此呢,它们也会从结果中去除任何 monotonic clock 读数
上面这些方法,去除 monotonic clock 的标准方式就是运行 t = t.Round(0)
,也就是四舍五入取整。
如果 Time
结构的对象 t
和 u
同时都包含了 monotonic clock 读数,那么 t.After(u)
、t.Before(u)
、t.Equal(u)
和t.Sub(u)
仅仅只会使用 monotonic clock 读数进行计算,而忽略 wall time 读数
但,只要 t
和 u
中的任何一个对象实例不包含 monotonic clock,那么这些上面这些方法就会回退到使用 wall clock 而忽略另一个实例的 monotonic clock
解释器来有点费劲,我们直接看源码
func (t Time) Before(u Time) bool { if t.wall&u.wall&hasMonotonic != 0 { return t.ext < u.ext } return t.sec() < u.sec() || t.sec() == u.sec() && t.nsec() < u.nsec() }
上面这些方法,无不例外,都有下面这段语句
if t.wall&u.wall&hasMonotonic != 0 { return t.ext < u.ext }
当 hasMonotonic !=0
,也就是存在 monotonic clock,就会直接比较 ext
这个属性,也就是 monotonic clock 读数
因为 monotonic clock 的特性,在某些系统上,如果计算机进入了睡眠状态,那么 monotonic clock 就会暂停 ( 不是停止 ),直到下一次计算机唤醒才会继续增加。在这样的系统上,因为 monotonic clock 的读数是不准确的,所以可能导致像 t.Sub(u)
无法准确反映 t
和 u
之间经过的实际时间
因为这种特性,所以 monotonic clock 读数在当前进程之外没有任何意义,直接导致了 t.GobEncode
、t.MarshalBinary
、t.MarshalJSON
和 t.MarshalText
生成的序列化结果省略了 monotonic clock 读数
更重要的是,t.Format
也忽略了 monotonic clock 读数,没有任何格式化字符来使用它
比上面更惨的是 ( 对 monotonic clock 而言 ),构造函数有 time.Date
、time.Parse
,time.ParseInLocation
和 time.Unix
,以及一些反序列化方法,比如 t.GobDecode
,t.UnmarshalBinary
、t.UnmarshalJSON
和 t.UnmarshalText
总是创建没有 monotonic clock 读数的时间
当然了,对 monotonic clock 而言,还是有好消息的,那就是 Go 语言中的 ==
运算符不仅比较 wall time ,还比较 Location 和 monotonic clock 读数
对于调试,利好消息就是,t.String
的结果会包括 monotonic clock 读数 ( 如果存在 ) 。针对 monotonic clock 读数的不同导致的 t != u
,可以使用打印 t.String()
和 u.String()
来看看到这种不同之处。