`
cwqcwk1
  • 浏览: 86786 次
文章分类
社区版块
存档分类
最新评论

从erlang时间函数说到时间校正机制

 
阅读更多
很多人会注意到这个问题,erlang提供了2个时间函数,erlang:now() 和os:timestamp()。用法一样,都是返回当前的时间。具体时间是从1970年1月1日零时算起,到现在经过的时间,结果为{MegaSecs, Secs, MicroSecs}。

这两个函数有什么区别?

os:timestamp() 获取到的时间为操作系统的时间,不做任何修正;而erlang:now(),每次获取都会确保生成了唯一的时间,就是说,erlang:now()在实现上对时间做了一个校正,每次都生成一个单调向前的唯一值。

erlang:now()的特点:

Monotonic
erlang:now() never jumps backwards - it always moves forward
Interval correct
The interval between two erlang:now() calls is expected to correspond to the correct time in real life (as defined by an atomic clock, or better)
Absolute correctness
The erlang:now/0 value should be possible to convert to an absolute and correct date-time, corresponding to the real world date and time (the wall clock)
System correspondence
The erlang:now/0 value converted to a date-time is expected to correspond to times given by other programs on the system (or by functions like os:timestamp/0)
Unique
No two calls to erlang:now on one Erlang node should return the same value

主要是这3个特点:
特点
说明
单调向前
erlang:now() 获取的时间是单调向前,就算系统时间倒退了,也不会影响这个函数的使用。(时间依旧是向前的,较之前几乎没有偏差)
唯一性
erlang:now() 获取的值都是唯一的,不会重复出现2个相同的值。
间隔修正
两次 erlang:now() 调用的间隔都可以被利用来修正erlang时间。
到这里,可以看出 erlang 内部实现了一套时间校正的机制,当系统时间出错的时候,就会做修正。(关于这块内容,可以看Erlang相关文档time correction

erlang 时间校正

时间校正的作用:

在开始这段内容前,讲讲时间校正的作用
1. 时间单调向前:
举个例子,说明时间倒退问题:
比如,游戏中会统计今天和昨天杀怪的总数量,跨零点时要把今天杀怪字段的数量写到昨天的字段,然后将今天的置0。跨零点后,如果时间倒退了几秒钟,然后就会重复跨零点。那么,今天的数量会覆盖昨天的数量,导致昨天的数量被清零。

2. 时间平稳:
同样举个例子,说明时间不平稳问题:
比如,erlang开发中,经常都会出现一个进程call另一个进程的场景,一般是5秒超时,假如时间突然加快了5秒,就相当于没有等待操作完成,就直接超时了。当然这是很不合理的

erlang时间校正的特点:

Monotonic
The clock should not move backwards
Intervals should be near the truth
We want the actual time (as measured by an atomic clock or an astronomer) that passes between two time stamps, T1 and T2, to be as near to T2 - T1 as possible.
Tight coupling to the wall clock
We want a timer that is to be fired when the wall clock reaches a time in the future, to fire as near to that point in time as possible

假如操作系统时间出现了改变,erlang不会立刻改变内部时间为系统时间,而是将时间轻微加快或减慢,最终和系统时间保持一致。就算系统时间突然倒退到以前的某个时间,但时间总是向前这点是不会改变的,所以,erlang只是预期在将来某个时间和系统时间达成一致,而不会倒退时间。

erlang是怎么校正时间的?

erlang内部时间会和系统挂钟时间保持同步,当系统挂钟时间突然改变时,erlang会比较两个时间的差异,让内部的时间的同步值轻微变大或变小,幅度最大是1%,就是说,VM经历 1s 实际上可能就是 0.99s 或者1.01s。当系统时间改变了1分钟,erlang会花100分钟来慢慢校正,并最终和系统时间保持同步。

哪些函数受到时间校正影响?

erlang:now/0
The infamous erlang:now/0 function uses time correction so that differences between two "now-timestamps" will correspond to other timeouts in the system. erlang:now/0 also holds other properties, discussed later.
receive ... after
Timeouts on receive uses time correction to determine a stable timeout interval.
The timer module
As the timer module uses other built in functions which deliver corrected time, the timer module itself works with corrected time.
erlang:start_timer/3 and erlang:send_after/3
The timer BIF's work with corrected time, so that they will not fire prematurely or too late due to changes in the wall clock time.
不只是 erlang:now() ,以上几个功能都有赖于时间校正的实现。比如 erlang:send_after/3 , 就算系统时间改变了,这个函数发出的消息也会按预定时间期限送达。

源码剖析

erlang:now() 是 bif 实现,代码如下:(以R16B02为例)
/*
 * bif.c now_0函数,实现 erlang:now/0
 * return a timestamp
 */
BIF_RETTYPE now_0(BIF_ALIST_0)
{
    Uint megasec, sec, microsec;
    Eterm* hp;

    get_now(&megasec, &sec, &microsec); // 获取当前时间
    hp = HAlloc(BIF_P, 4);
    BIF_RET(TUPLE3(hp, make_small(megasec), make_small(sec),
		   make_small(microsec))); // 返回{MegaSecs, Secs, MicroSecs}
}
再来看下 get_now() 函数。
/*
 * erl_time_sup.c get_now函数,获取当前时间
 * get a timestamp
 */
void get_now(Uint* megasec, Uint* sec, Uint* microsec)
{
    SysTimeval now;
    
    erts_smp_mtx_lock(&erts_timeofday_mtx);
    
    get_tolerant_timeofday(&now); // 获取当前时间值
    do_erts_deliver_time(&now);  // 记录当前的时间(用于VM内部读取当前时间,如timer)

    /* 确保时间比上次获取的大 */
    if (then.tv_sec > now.tv_sec ||
	(then.tv_sec == now.tv_sec && then.tv_usec >= now.tv_usec)) {
	now = then;
	now.tv_usec++;
    }
    /* Check for carry from above + general reasonability */
    if (now.tv_usec >= 1000000) {
	now.tv_usec = 0;
	now.tv_sec++;
    }
    then = now;
    
    erts_smp_mtx_unlock(&erts_timeofday_mtx);
    
    *megasec = (Uint) (now.tv_sec / 1000000);
    *sec = (Uint) (now.tv_sec % 1000000);
    *microsec = (Uint) (now.tv_usec);

    update_approx_time(&now);//更新「简要」时间(仅用于标记进程启动时间)
}
这里重点看下get_tolerant_timeofday(),实现了时间校正功能。
/*
 * erl_time_sup.c get_tolerant_timeofday函数,获取当前时间
 * 根据系统API不同有两种实现,这里取其中一种做说明
 */
static void get_tolerant_timeofday(SysTimeval *tv)
{
    SysHrTime diff_time, curr;

    if (erts_disable_tolerant_timeofday) {// 时间校正功能被禁用,直接返回系统时间
	sys_gettimeofday(tv);
	return;
    }
    *tv = inittv; // 取VM启动时间
	
	// 计算从VM启动到现在经过的内部时间(正值,单位微秒)
    diff_time = ((curr = sys_gethrtime()) + hr_correction - hr_init_time) / 1000; 

    if (curr < hr_init_time) {
	erl_exit(1,"Unexpected behaviour from operating system high "
		 "resolution timer");
    }

	// 检查是否刚校正过(两次校正最小间隔 1s)
    if ((curr - hr_last_correction_check) / 1000 > 1000000) {
	/* Check the correction need */
	SysHrTime tv_diff, diffdiff;
	SysTimeval tmp;
	int done = 0;

	// 计算从VM启动到现在经过的实际时间(如果系统时间被调整过,可能是负值,单位微秒)
	sys_gettimeofday(&tmp);
	tv_diff = ((SysHrTime) tmp.tv_sec) * 1000000 + tmp.tv_usec;
	tv_diff -= ((SysHrTime) inittv.tv_sec) * 1000000 + inittv.tv_usec;
	diffdiff = diff_time - tv_diff;// 实际时间与内部时间的差值(缩短这个时间差以赶上实际时间)
	if (diffdiff > 10000) { // 内部时间比外部时间快 0.01s 以上
	    SysHrTime corr = (curr - hr_last_time) / 100; //  两次调用经过的实际时间 * 1%
	    if (corr / 1000 >= diffdiff) {
			++done;
			hr_correction -= ((SysHrTime)diffdiff) * 1000; 
			/* 超过diffdiff*1000 * 100,只修正 diffdiff*1000,
			 * 就是1s需要花100s修正,同时标记本次修正完成
			 * 什么情况下会走到这里:就是这个函数很久没调用,超过了时间偏差的100倍
			 * 然后标记修正完成,至此,就没有时间偏差了
			 */
	    } else {
		hr_correction -= corr; // 修正值为两次调用经过的实际时间 * 1%
	    }
		// 重算与VM启动时间的间隔
	    diff_time = (curr + hr_correction - hr_init_time) / 1000; 
	} else if (diffdiff < -10000) { // 内部时间比外部时间慢 0.01s 以上
	    SysHrTime corr = (curr - hr_last_time) / 100;
	    if (corr / 1000 >= -diffdiff) {
		++done;
		hr_correction -= ((SysHrTime)diffdiff) * 1000;
	    } else {
		hr_correction += corr;
	    }
	    diff_time = (curr + hr_correction - hr_init_time) / 1000; 
	} else {
	    /* 内部时间与外部时间偏差在0.01s 内,标记完成,等1s后修正剩下的时间
	     * 这段代码目的是,如果时间偏差在0.01s内,VM特意等1s后修正这个时间
	     * 另外,如果时间没出差错,就都走到这里,减少时间函数调用开销
         */
	    ++done;
	}
	if (done) {
	    hr_last_correction_check = curr;
	}
    }
    tv->tv_sec += (int) (diff_time / ((SysHrTime) 1000000));
    tv->tv_usec += (int) (diff_time % ((SysHrTime) 1000000));
    if (tv->tv_usec >= 1000000) {
	tv->tv_usec -= 1000000;
	tv->tv_sec += 1;
    }
    hr_last_time = curr;
}
这里,erlang利用一个单调递增的时间函数sys_gethrtime(),作为参照物来判断VM实际经历的真实时间,然后再轻微的向系统挂钟时间倾斜,以致最终和系统挂钟时间保持同步。至于sys_gethrtime(),我也准备了一点资料,放在拓展阅读分享吧。


拓展阅读

gethrtime()

前面提到的sys_gethrtime(),实际上是一个宏(暂时只讨论linux下的实现,win下类似)
#define sys_gethrtime() gethrtime()
关于 gethrtime() 可以看下unix官方文档说明man page for gethrtime,写得很详细。
gethrtime() 作用是用于实时获取当前时间,精度非常高,以纳秒为单位,但是,两次足够紧连的调用有可能返回相同的结果,毕竟精度单位是纳秒,可以保证的是时间不会倒退。
另外,这里参考了C ++参考指南C++ Reference Guide -High Resolution Timers

也就是这两个特点:
1. 单调向前,不会倒退;
2. 线性增长,不会变快或变慢。每个时间刻度经历的时间都是一样的。

所以,Erlang VM利用基于硬件的单调递增时间,取两个时刻的差值来计算VM运行的时间,然后取操作系统的挂钟时间做比较,通过逐渐缩小这个时间差来实现VM时间始终单调向前,从而平稳地把VM时间纠正到用户的挂钟时间。

时间同步

这里看到《并行与分布仿真系统》的作者写的相关文章Synchronizing Clocks
(Wallclock Time),也很有参考价值

假设时间提前了10毫秒,就每隔30毫秒产生一次中断,每次中断的增量时间为29毫秒,花10次完成修正。

结束语

最后,说下时间校正的副作用。
erlang实现时间校正有计算开销的,而且这个内部校正值是全局变量,不只是所有erlang进程,还是VM所有调度线程都会读写这个时间,所以就要有锁来保证数据安全。为此,erlang内部设定好了 erlang:now/0 调用频率不会超过1微妙1次。
当然,如果获取时间只是用于测试目的,或者打印错误日志时间,完全可以用os:timestamp/0 来代替。对于一些有大规模进程的项目,还可以设立一些时间管理进程,用于同步时间,而每个进程只要读取自己的进程字典就好。

如果不想使用,还可以禁用这个功能。
Time correction is enabled or disabled by passing the +c [true|false] command line argument to erl.

R18之后,erlang提供了更多时间校正相关的API,对用户暴露底层时间的相关信息。这里暂时就不说明了。链接地址

2015/5/5 修正标题(时间校正体系 => 时间校正机制)
参考:http://blog.csdn.net/mycwq/article/details/45346411
分享到:
评论

相关推荐

    基于ErlangC函数的Oracle性能预测和分析.pdf

    Erlang C 函数是一种经典的呼叫中心模型,常用于预测在一定并发用户数下系统的性能,如响应时间和服务质量。在本文中,作者将Erlang C函数应用于Oracle数据库性能预测,为数据库管理员提供了有效评估和优化数据库...

    erlang的timer和实现机制

    因此,编写健壮的Erlang代码需要考虑到这些可能的异常情况,并采取适当的错误处理策略。 在深入理解Erlang的timer模块的同时,还可以查阅《Erlang程序设计》这本书,它提供了更多关于Erlang语言特性和实践的详细...

    erlang编程 Introducing Erlang

    Erlang是一种函数式编程语言,由爱立信在1986年开发,主要用于构建高可用性、容错性和并发性的分布式系统。"Introducing Erlang"是Simon St. Laurent撰写的一本入门级教程,旨在帮助初学者理解和掌握Erlang的核心...

    erlang资源

    Erlang是一种面向并发的、函数式编程语言,由瑞典...这两本书结合阅读,将为初学者提供一个全面的Erlang学习路径,从基础语法到高级并发编程技巧,有助于深入理解Erlang语言及其在构建高并发、分布式系统中的强大能力。

    xiandiao_erlang_Erlang课后习题_

    1. **函数式编程**:Erlang基于函数式编程范式,强调无副作用的纯函数,以及通过数据不可变性来简化并发处理。在Erlang中,程序是由一系列相互独立的函数构成的,它们可以并行执行,提高了系统的性能。 2. **并发与...

    erlang整理的一些心得和lunix查看cpu和内存信息的方法

    6. **OTP(Open Telecom Platform)**:OTP 是一组设计原则、库和工具,为Erlang应用提供了标准框架,包括行为、设计模式和错误管理机制。 ### Linux 查看 CPU 和内存信息 1. **top 命令**:实时显示系统总体的CPU...

    Programming Erlang

    然后,读者将学习到Erlang的函数式编程思想,包括函数定义、模式匹配、匿名函数(也称为闭包)以及函数组合。 Erlang的并发模型是其核心优势之一。书中会详细讨论进程(process),它们在Erlang中的地位相当于轻量...

    erlang_版本24.3.4.4

    - **错误处理**:Erlang采用异常处理机制,鼓励编写无副作用的纯函数,有助于编写容错性强的代码。 - **模式匹配**:Erlang的模式匹配功能允许在函数定义中使用模式来匹配和解构数据结构,简化了代码编写。 - **...

    erlang中文基础教程

    1. **函数式编程**:Erlang采用函数式编程范式,强调不可变数据和纯函数,这使得代码更易于理解和测试。 2. **并发性**:Erlang中的进程是轻量级的,创建和销毁几乎无开销,且进程间通过消息传递通信,降低了共享...

    两本erlang电子书

    最后,第三部分通过构建实际项目,演示如何在实际环境中应用Erlang和OTP,涵盖了从系统设计到部署的全过程。 《Erlang程序设计》则更侧重于Erlang语言本身,适合初学者入门。书中会详细解释Erlang的语法和编程范式...

    Erlang入门

    总结来说,Erlang以其独特的函数式编程范式、强大的并发和分布式能力,以及热代码升级等功能,为构建高可靠性的系统提供了强大工具。通过深入学习和实践,开发者能够掌握构建大规模并发系统的关键技能。

    erlang25.0 windows版本

    3. **环境变量**:安装过程中,安装程序可能会自动添加Erlang的bin目录到系统的PATH环境变量中,确保命令行可以识别`erl`等Erlang命令。 4. **验证**:安装完成后,打开命令行窗口并输入`erl`,如果Erlang成功安装,...

    erlang 程序设计 源码

    8. **错误处理**:Erlang的错误处理通常通过异常机制进行,如果捕获到异常,可以决定是否恢复执行或退出进程。 9. **网络编程**:Erlang天生适合网络编程,因为其内置了对TCP、UDP等协议的支持,可以轻松创建分布式...

    《Programming Erlang》

    《Programming Erlang》是Joseph Armstrong所著的一本详细介绍Erlang...书中的例子涵盖了从基本的函数式编程概念到复杂的并发系统设计,对于任何想要涉足Erlang或对函数式编程感兴趣的人来说,都是一本不可多得的资源。

    windows下安装Erlang环境

    **函数式编程**:Erlang是一种函数式语言,函数无副作用,强调纯函数和不可变数据,这有助于编写简洁、可预测的代码。 **动态类型**:Erlang是动态类型的,变量的类型在运行时确定,提供了灵活性。 **及早求值或...

    Erlang_CNode用户指

    总结来说,Erlang_CNode用户指南是为希望将Erlang与C语言结合使用的开发者准备的宝贵资料。它不仅涵盖了如何在C环境中搭建和使用CNode,还介绍了如何利用Erlang的并发特性和强大的错误处理能力。同时,提供的更多...

    Erlang趣学指南

    (494页带目录的高清扫描版) 这是一本讲解Erlang编程语言的入门指南,内容通俗...内容涉及模块、函数、类型、递归、错误和异常、常用数据结构、并行编程、多处理、OTP、事件处理,以及所有Erlang的重要特性和强大功能。

Global site tag (gtag.js) - Google Analytics