浅析libuv源码-获取精确时间

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

  若是是JS,获取当前时间能够直接经过Date.now()获得一个时间戳,而后将两段时间戳相减获得时间差。通常状况下固然没有问题,可是这个方法并不保险,由于本地计算机时间能够修改。安全

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

 

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

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; 

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

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

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_,从名字能够看出这是一个表明频率的数字,相关的定义和设置代码以下:code

/* 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  并不依赖于本地时间,因此计算获得的数值能够保证绝对安全。

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

相关文章
相关标签/搜索