时间系统一般在程序中有以下几个层次:
1. 绝对时间
2. 本机系统时间
3. 本进程运行时间
4. 用户自定义的时间(差分系统时间)
根据不同的精度需求一般又分为:
1. 日历时间
2. 高精度时间
根据场景不同,又可分为:
1. 单系统,单线程
2. 单系统,多线程(多进程)
3. 跨系统,多线程(多进程)
linux系统与windows系统又略有不同,这里总结下最近对时间系统的理解。
日历时间,往往以年,月,日,时,分,秒的格式出现,这是对精度要求不高的时间需求。
在windows API中,获取时间的函数:
CTime::GetCurrentTime();
GetLocalTime(&st);
linux下,用time_t数据类型来保存,是自1970年1月1日00:00:00以来国际标准时间所经过的描述累计值。
time_t 一般是一个long型来表示,单位是秒
而struct tm是一个结果 以年,月,日,时,分,秒等表示
相关函数: ctime()、gmtime()、localtime()、asctime()、mktime()、strfitme()
一些函数是time_t与结构tm直接的相互转换:
localtime和gmtime都是将日历时间转换为年月日时分秒表示的时间,并存入struct tm的结构中,两个区别:前者考虑转换成本地时间,考虑了市区和夏时制标志;后者则转换为国际的标准时间。
mktime则是将struct tm转换为time_t,将结构转换为秒数
ctime和astime则是将时间转换为时间字符串,区别是两者的入参一个是time_t,另一个是struct tm
time(time_t)是获取秒数。
且以上函数是单线程下使用,不是多线程安全的,若在多线程下,应使用:
char *asctime_r(const struct tm *tm, char *buf);
char *ctime_r(const time_t *timep, char *buf);
struct tm *gmtime_r(const time_t *timep, struct tm *result);
struct tm *localtime_r(const time_t *timep, struct tm *result);
以上仅仅是与日历时间相关,精度是秒。
但在实时系统中,很多事需要高精度时间的比较:
所以,需要其他函数的支持:
windows下:
getTickCount 10ms精度,其实是tick的精度,如果tick的精度是22ms,那么这个函数的精度是22ms。很多毫秒标称的函数,其精度都是粗于1毫秒的。
QueryPerfermanceCount / QueryPerformanceFrequency 约100ns精度
而linux下,提供了两个结构来描述,一个微秒级,一个纳秒级
struct timeval
{
time_t tv_sec;
suseconds_t tv_usec;
};
对应的函数gettimeofday
struct timespec
{
time_t tv_sec;
long tv_nsec;
};
对应的函数是 clock_gettime()
这两个函数都是可重入的,即线程安全的。
在程序要求统计精确的时间时,常用的是clock()函数。
其通常是统计单进程的运行时间。但其精度要粗于ms。并且,其无法统计子进程的时间,linux下无法,区分用户空间和进程空间。
而times函数/系统调用,能区分进程、子进程的用户代码和内核代码的执行时间。其返回的结构是struct tms;
但clock和times函数的精度一般为10ms,或更糟。
1. 绝对时间获取
使用场景:跨机系统,多线程开发
在linux系统中,是自1970年1月1日00:00:00以来国际标准时间所经过的描述累计值,使用这个很方便。timespec或timeval结构能比较方便的提供高精度的时钟。
而在windows下,GetTimeTick(),及QueryPerformanceCounter()都是从本机系统启动开始统计。因此,其需要外部时间服务器来支持。
2. 本机系统时间systemTick
使用场景:本机系统,多线程或单线程开发
Linux和Windows都能支持这种方式。
3. 本线程运行时间
使用场景:clock()函数。在简单的单线程开发中使用。
4. 用户自定义的时间
使用场景:较复杂的系统,拥有自定义时间服务的类库
a. 提供统一的时间戳接口。可以分别封装linux和windows的时间函数。
b. 差分时钟服务。更进一步的封装,提供一个timer类,当实例化的timer类 start时,记录下当前的绝对时间或系统时间startTime,以后每次从timer获取时间时,只需要提供一个时间差,(currentTime - startTime)。这样就能简化时间的表示形式,只需一个整型数表示时间,不用担心溢出的问题。这在日志等服务里使用比较好。
最后,时间的表示问题:
当用一个整型数来表示时间的秒数或毫秒数时,很容易造成溢出。有三种方法处理:
1. 采用64位计数器。Int64,如果当前硬件/系统支持,就采用64位表示,只不过其开销大些,但编程使用时比较方便。
2. 采用分段式计数,如timeval,timespec,一个统计秒数,另外一个统计us或ns。在linux系统下,比较方便。
3. 采用相对系统启动时间。
无论哪种方式,根据实际需求,要注意time时间的表示方式在当前程序中是否会溢出。如果可能溢出,就要挑选一种稳妥的方式来表示。