在 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 并不依赖于本地时间, 所以计算得到的数值可以保证绝对安全.
不过, 这个数字的计算方式, 简直跟时间戳一模一样啊.
来源: https://www.cnblogs.com/QH-Jimmy/p/10080857.html