经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C++ » 查看文章
浅析libuv源码-获取精确时间
来源:cnblogs  作者:书生小龙  时间:2018/12/7 9:33:18  对本文有异议

  在Timer模块中有提到,libuv控制着延迟事件的触发,那么必须想办法精确控制时间。

  如果是JS,获取当前时间可以直接通过Date.now()得到一个时间戳,然后将两段时间戳相减得到时间差。一般情况下当然没有问题,但是这个方法并不保险,因为本地计算机时间可以修改。

  libuv显然不会用这么愚蠢的办法来计算时间,C++内部有更为精妙的方法来处理这个事。

 

  首先在上一节中,一个简单的事件轮询代码如下:

  1. int main() {
  2. uv_loop_t *loop = uv_default_loop();
  3. uv_run(loop, UV_RUN_DEFAULT);
  4. }

  这里的uv_default_loop会生成一个默认的静态对象,负责管理事件轮询,而这个对象有一个属性,则负责记录当前的时间,如下:

  1. /* The current time according to the event loop. in msecs. */
  2. uint64_t time;

  简单讲就是记录当前这一轮事件开始处理的时间,单位为毫秒。

  在初始化之后,就会执行uv_run来开始事件轮询了,因为这节只讲时间,所以省略无关代码,如下:

  1. int uv_run(uv_loop_t *loop, uv_run_mode mode) {
  2. // ...
  3. // 查询是否有未处理事件
  4. r = uv__loop_alive(loop);
  5. if (!r)
  6. // 表示处理完一轮事件 更新时间
  7. uv_update_time(loop);
  8. // 如果有未处理事件
  9. while (r != 0 && loop->stop_flag == 0) {
  10. // 这里也会更新时间
  11. uv_update_time(loop);
  12. // ...
  13. }
  14. }

  可见,每次轮询时都会更新时间,方法就是那个uv_update_time,源码如下:

  1. void uv_update_time(uv_loop_t* loop) {
  2. // 返回一个时间
  3. uint64_t new_time = uv__hrtime(1000);
  4. // 检测数据合法性并赋值
  5. assert(new_time >= loop->time);
  6. loop->time = new_time;
  7. }
  8. uint64_t uv__hrtime(double scale) {
  9. LARGE_INTEGER counter;
  10. if (hrtime_interval_ == 0) {
  11. return 0;
  12. }
  13. if (!QueryPerformanceCounter(&counter)) {
  14. return 0;
  15. }
  16. return (uint64_t)((double)counter.QuadPart * hrtime_interval_ * scale);
  17. }

  上面的方法通过一些计算,会返回一个类似于时间戳的长整数。

  C++的方法都比较简单,首先看一下hrtime_interval_,从名字可以看出这是一个代表频率的数字,相关的定义和设置代码如下:

  1. /* Interval (in seconds) of the high-resolution clock. */
  2. static double hrtime_interval_ = 0;
  3. /*
  4. * One-time initialization code for functionality defined in util.c.
  5. */
  6. void uv__util_init(void) {
  7. LARGE_INTEGER perf_frequency;
  8. /* 加锁 不管这个 */
  9. InitializeCriticalSection(&process_title_lock);
  10. /* Retrieve high-resolution timer frequency
  11. * and precompute its reciprocal.
  12. */
  13. if (QueryPerformanceFrequency(&perf_frequency)) {
  14. hrtime_interval_ = 1.0 / perf_frequency.QuadPart;
  15. }
  16. else {
  17. hrtime_interval_ = 0;
  18. }
  19. }

  该值的初始化为0,然后会通过某个计算尝试重新赋值。

  这里需要介绍一下两个windowsAPI: QueryPerformanceFrequency 与 QueryPerformanceCounter 。

  定义非常简单,字面理解一个是系统性能频率,一个是系统性能计数器,具体讲,第一个会返回当前操作系统每秒钟会统计多少次,第二个返回当前已经统计的次数(类似于时间戳从1970年开始,这个应该也有一个参照物),依赖于硬件支持,如果不支持会返回0。

  可以通过一个简单的案例来理解这两个API,测试代码如下:

  1. int main() {
  2. LARGE_INTEGER m;
  3. LARGE_INTEGER n1;
  4. LARGE_INTEGER n2;
  5. // 获取每秒钟统计的次数
  6. QueryPerformanceFrequency(&m);
  7. for (int i = 0; i < 5; i++) {
  8. // 获取当前的统计次数
  9. QueryPerformanceCounter(&n1);
  10. // zzz...线程等待一秒
  11. Sleep(1000);
  12. // 获取一秒后统计次数
  13. QueryPerformanceCounter(&n2);
  14. // 计算sleep方法实际时间
  15. cout << "过去了" << (double)(n2.QuadPart - n1.QuadPart) / (double)m.QuadPart << "" << endl;
  16. }
  17. return 0;
  18. }

  执行后输出如下:

  可见,系统的1秒钟实际上并不十分精确。

  回到hrtime_interval_的定义:

  1. hrtime_interval_ = 1.0 / perf_frequency.QuadPart;

  很容易知道这里返回的是系统每计数一次所需要的时间。

  然后可以理解uv_hrtime方法具体的返回:

  1. uint64_t uv__hrtime(double scale) {
  2. LARGE_INTEGER counter;
  3. // 如果硬件不支持 返回0
  4. if (hrtime_interval_ == 0) {
  5. return 0;
  6. }
  7. // 获得当前计数
  8. if (!QueryPerformanceCounter(&counter)) {
  9. return 0;
  10. }
  11. // 返回当前计数所花费的时间 默认为秒scale(1000)转换为毫秒
  12. return (uint64_t)((double)counter.QuadPart * hrtime_interval_ * scale);
  13. }

  由于 QueryPerformanceFrequency  与 QueryPerformanceCounter  并不依赖于本地时间,所以计算得到的数值可以保证绝对安全。

  不过,这个数字的计算方式,简直跟时间戳一模一样啊。

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号