浅析libuv源码-获取精确时间
2018-12-09 11:18:04来源:博客园 阅读 ()
在Timer模块中有提到,libuv控制着延迟事件的触发,那么必须想办法精确控制时间。
如果是JS,获取当前时间可以直接通过Date.now()得到一个时间戳,然后将两段时间戳相减得到时间差。一般情况下当然没有问题,但是这个方法并不保险,因为本地计算机时间可以修改。
libuv显然不会用这么愚蠢的办法来计算时间,C++内部有更为精妙的方法来处理这个事。
首先在上一节中,一个简单的事件轮询代码如下:
int main() { uv_loop_t *loop = uv_default_loop(); uv_run(loop, UV_RUN_DEFAULT); }
这里的uv_default_loop会生成一个默认的静态对象,负责管理事件轮询,而这个对象有一个属性,则负责记录当前的时间,如下:
/* The current time according to the event loop. in msecs. */ uint64_t time;
简单讲就是记录当前这一轮事件开始处理的时间,单位为毫秒。
在初始化之后,就会执行uv_run来开始事件轮询了,因为这节只讲时间,所以省略无关代码,如下:
int uv_run(uv_loop_t *loop, uv_run_mode mode) { // ... // 查询是否有未处理事件 r = uv__loop_alive(loop); if (!r) // 表示处理完一轮事件 更新时间 uv_update_time(loop); // 如果有未处理事件 while (r != 0 && loop->stop_flag == 0) { // 这里也会更新时间 uv_update_time(loop); // ... } }
可见,每次轮询时都会更新时间,方法就是那个uv_update_time,源码如下:
void uv_update_time(uv_loop_t* loop) { // 返回一个时间 uint64_t new_time = uv__hrtime(1000); // 检测数据合法性并赋值 assert(new_time >= loop->time); loop->time = new_time; } uint64_t uv__hrtime(double scale) { LARGE_INTEGER counter; if (hrtime_interval_ == 0) { return 0; } if (!QueryPerformanceCounter(&counter)) { return 0; } return (uint64_t)((double)counter.QuadPart * hrtime_interval_ * scale); }
上面的方法通过一些计算,会返回一个类似于时间戳的长整数。
C++的方法都比较简单,首先看一下hrtime_interval_,从名字可以看出这是一个代表频率的数字,相关的定义和设置代码如下:
/* Interval (in seconds) of the high-resolution clock. */ static double hrtime_interval_ = 0; /* * One-time initialization code for functionality defined in util.c. */ void uv__util_init(void) { LARGE_INTEGER perf_frequency; /* 加锁 不管这个 */ InitializeCriticalSection(&process_title_lock); /* Retrieve high-resolution timer frequency * and precompute its reciprocal. */ if (QueryPerformanceFrequency(&perf_frequency)) { hrtime_interval_ = 1.0 / perf_frequency.QuadPart; } else { hrtime_interval_ = 0; } }
该值的初始化为0,然后会通过某个计算尝试重新赋值。
这里需要介绍一下两个windowsAPI: QueryPerformanceFrequency 与 QueryPerformanceCounter 。
定义非常简单,字面理解一个是系统性能频率,一个是系统性能计数器,具体讲,第一个会返回当前操作系统每秒钟会统计多少次,第二个返回当前已经统计的次数(类似于时间戳从1970年开始,这个应该也有一个参照物),依赖于硬件支持,如果不支持会返回0。
可以通过一个简单的案例来理解这两个API,测试代码如下:
int main() { LARGE_INTEGER m; LARGE_INTEGER n1; LARGE_INTEGER n2; // 获取每秒钟统计的次数 QueryPerformanceFrequency(&m); for (int i = 0; i < 5; i++) { // 获取当前的统计次数 QueryPerformanceCounter(&n1); // zzz...线程等待一秒 Sleep(1000); // 获取一秒后统计次数 QueryPerformanceCounter(&n2); // 计算sleep方法实际时间 cout << "过去了" << (double)(n2.QuadPart - n1.QuadPart) / (double)m.QuadPart << "秒" << endl; } return 0; }
执行后输出如下:
可见,系统的1秒钟实际上并不十分精确。
回到hrtime_interval_的定义:
hrtime_interval_ = 1.0 / perf_frequency.QuadPart;
很容易知道这里返回的是系统每计数一次所需要的时间。
然后可以理解uv_hrtime方法具体的返回:
uint64_t uv__hrtime(double scale) { LARGE_INTEGER counter; // 如果硬件不支持 返回0 if (hrtime_interval_ == 0) { return 0; } // 获得当前计数 if (!QueryPerformanceCounter(&counter)) { return 0; } // 返回当前计数所花费的时间 默认为秒scale(1000)转换为毫秒 return (uint64_t)((double)counter.QuadPart * hrtime_interval_ * scale); }
由于 QueryPerformanceFrequency 与 QueryPerformanceCounter 并不依赖于本地时间,所以计算得到的数值可以保证绝对安全。
不过,这个数字的计算方式,简直跟时间戳一模一样啊。
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
上一篇:new实现
- Java固定资产管理系统 源码 jsp ssh 2020-01-09
- Qt事件分发机制源码分析之QApplication对象构建过程 2019-12-08
- STL源码剖析——序列式容器#5 heap 2019-11-07
- stl源码学习(版本2.91)--list 2019-11-05
- webbench网站测压工具源码分析 2019-10-12
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash