Erlang虚拟机可视为一个小型操作系统, 有自己的一套时钟系统, 与系统时间并不是完全一致的.

Erlang虚拟机时钟使用的是 erlang:now() 获取, 系统时间使用 os:timestamp() 获取. 格式是一样的三元组: {百万秒, 秒, 微秒}.

Erlang内部时钟的特点

为什么Erlang需要实现自己的虚拟机时钟呢? 因为系统时钟有时是不可靠的, 可能会因为联网自动对时或是管理员调时钟而发生突变或往回走, Erlang虚拟机时钟就是为了避免这些问题而设计的, 它有以下几个特点:

  1. 单向: 永远不会往回走

  2. 时间间隔接近于真实: 如果在两个时间读取虚拟机时钟, 读数为T1和T2, 那么T1-T2的值接近于两个时间点的真实时间间隔.

  3. 自动修正: 如果Erlang发现虚拟机内部时钟与系统时钟读数不一致, 它会自动缓慢地修正内部时钟, 使内部时钟与系统时钟一致.

第二点是说: 如果两次读取系统时钟, 发现时间相差1小时, 不一定是真实时间过了一小时, 可能是时钟被拨快了1小时, 实际上只过了几秒 但Erlang虚拟机时钟则可以保证, 读数相差1小时, 那么实际时间过了大约一小时, 误差不超过1%.

第三点的实现方法是, Erlang发现内部时间和系统时间差距"较大"的话, 会调节内部时钟走时速度来适应外部时钟. 比方说外部时钟突然快了1分钟, Erlang会用100分钟来"追赶"外部时钟, 这段时间内Erlang内部时钟走时会快1%.

Erlang的这套时钟修正机制虽然不是完美的, 但可以避免出现时间跳跃问题, 防止定时器过早或过晚触发.

Erlang内部时钟的应用

Erlang内部时钟在以下这个几地方使用:

  • erlang:now()
  • receive...after
  • timer模块
  • erlang:start_timer/3erlang:send_after/3

这就是说, 如果你用send_after设置1小时后给某进程发消息, 想调系统时间让消息快点到是不可能的, 老老实实等1小时吧.

erlang:now() 的特点

  1. 单向
  2. 间隔真实
  3. 自动修正
  4. 唯一

前三点由Erlang的内部时钟机制保证, 不必多说. 最后一点是指, 即使在多进程下, 每次调用erlang:now()都会获得不同的值. 因此erlang:now()的返回值也可以拿来当唯一ID用, 不用担心重复.

不过这个机制也会造成在高频率(每秒大于1百万次)调用时, erlang:now()的返回值有偏差. 因为它的返回值精度为1微秒, 1微秒调用2次的话, 也要值这个微秒数加2, 显得走快了.

当系统时间走时正常, 而且调用频率不高时, erlang:now()显示的时间和系统时间是一致的.

关闭内部时钟自动修正

可以在启动erl的时候, 使用+c参数, 关闭内部时钟的自动修正. 这是一般用不上的功能.

erlang:now() vs os:timestamp()

os:timestamp()返回的是系统时钟, 调系统时间的时候就会立即改变. 这对于进行时间相关的逻辑的测试时是很有用的.

erlang:now()在单进程下稍快于os:timestamp(), 但多进程竞争调用会变慢, 可以参考我在stackoverflow上的提问.


参考: Erlang Run-Time System Application (ERTS) 第1.2节