在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 != && loop->stop_flag == ) {
// 这里也会更新时间
uv_update_time(loop);
// ...
}
}

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

void uv_update_time(uv_loop_t* loop) {
// 返回一个时间
uint64_t new_time = uv__hrtime();
// 检测数据合法性并赋值
assert(new_time >= loop->time);
loop->time = new_time;
} uint64_t uv__hrtime(double scale) {
LARGE_INTEGER counter; if (hrtime_interval_ == ) {
return ;
} if (!QueryPerformanceCounter(&counter)) {
return ;
} return (uint64_t)((double)counter.QuadPart * hrtime_interval_ * scale);
}

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

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

/* Interval (in seconds) of the high-resolution clock. */
static double hrtime_interval_ = ; /*
* 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,然后会通过某个计算尝试重新赋值。

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

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

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

int main() {
LARGE_INTEGER m;
LARGE_INTEGER n1;
LARGE_INTEGER n2;
// 获取每秒钟统计的次数
QueryPerformanceFrequency(&m);
for (int i = ; i < ; i++) {
// 获取当前的统计次数
QueryPerformanceCounter(&n1);
// zzz...线程等待一秒
Sleep();
// 获取一秒后统计次数
QueryPerformanceCounter(&n2);
// 计算sleep方法实际时间
cout << "过去了" << (double)(n2.QuadPart - n1.QuadPart) / (double)m.QuadPart << "秒" << endl;
}
return ;
}

  执行后输出如下:

  可见,系统的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_ == ) {
return ;
}
// 获得当前计数
if (!QueryPerformanceCounter(&counter)) {
return ;
}
// 返回当前计数所花费的时间 默认为秒scale(1000)转换为毫秒
return (uint64_t)((double)counter.QuadPart * hrtime_interval_ * scale);
}

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

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

浅析libuv源码-获取精确时间的更多相关文章

  1. 浅析libuv源码-编译启动

    面试的间隙回头复习了一下node,感觉node就像一个胶带,把V8和libuv粘在了一起. V8毫无疑问,负责解析执行JavaScript,相当于语言层面的桥梁:而libuv则是负责操作系统底层功能的 ...

  2. 浅析libuv源码-node事件轮询解析(1)

    好久没写东西了,过了一段咸鱼生活,无意中想起了脉脉上面一句话: 始终保持自己的竞争力.所以,继续开写! 一般的JavaScript源码看的已经没啥意思了,我也不会写什么xx入门新手教程,最终决定还是啃 ...

  3. 浅析libuv源码-node事件轮询解析(3)

    好像博客有观众,那每一篇都画个图吧! 本节简图如下. 上一篇其实啥也没讲,不过node本身就是这么复杂,走流程就要走全套.就像曾经看webpack源码,读了300行代码最后就为了取package.js ...

  4. 浅析libuv源码-node事件轮询解析(4)

    这篇应该能结,简图如下. 上一篇讲到了uv__work_submit方法,接着写了. void uv__work_submit(uv_loop_t* loop, struct uv__work* w, ...

  5. 浅析libuv源码-node事件轮询解析(2)

    上一篇讲了轮询的边角料,这篇进入正题.(竟然真有人看我博客,上两个图给你们整理下思路) 这是轮询总流程图. 下图为本节内容简图. Poll for I/O The loop blocks for I/ ...

  6. libuv源码分析前言

    Libevent,libev,libuv三者的区别所在? libevent提供了全套解决方案(事件库,非阻塞IO库,http库,DNS客户端),然而libevent使用全局变量,导致非线程安全.它的w ...

  7. 小迪安全 Web安全 基础入门 第六天 - 信息打点-Web架构篇&域名&语言&中间件&数据库&系统&源码获取

    一 . Web架构 语言.常用的Web开发语言有PHP,Java,Python,JavaScript,.net等.具体可参考w3school的介绍. 中间件. (1)常见的Web服务器中间件:IIS. ...

  8. VC获取精确时间的做法

    声明:本文章是我整合网上的资料而成的,其中的大部分文字不是我所为的,我所起的作用只是归纳整理并添加我的一些看法.非常感谢引用到的文字的作者的辛勤劳动,所参考的文献在文章最后我已一一列出. 对关注性能的 ...

  9. 【安卓本卓】Android系统源码篇之(一)源码获取、源码目录结构及源码阅读工具简介

    前言        古人常说,“熟读唐诗三百首,不会作诗也会吟”,说明了大量阅读诗歌名篇对学习作诗有非常大的帮助.做开发也一样,Android源码是全世界最优秀的Android工程师编写的代码,也是A ...

随机推荐

  1. HTML给table添加单线边框

    一般来说,给表格加边框都会出现不同的问题,以下是给表格加边框后展现比较好的方式 <style> table,table tr th, table tr td { border:1px so ...

  2. bower 安装后 jade 引用404问题

    这个问题困扰我接近2小时,这是我在stackoveflow 上面挖到的 原文地址:http://stackoverflow.com/questions/21821773/configure-node- ...

  3. nginx,gunicorn常用命令

    nginx 启动: 在下载nginx的目录下直接输入nginx回车 停止: nginx -s stop 重启: nginx -s reload 查看当前运行进程: ps -ef | grep ngin ...

  4. Mac下IDE无法读取环境变量问题

    今天遇到一个问题,Idea无法读取~/.bash_profile下的配置文件. 上网查了好久,都说是launchctl的问题. 但是其实我这边是因为安装了zsh,导致环境标量失效. 在~/.zshrc ...

  5. 修改ssh远程默认端口

    修改ssh远程默认端口 Linuxssh端口修改 1. 修改ssh配置文件 [root@distzabbix ~]# vim /etc/ssh/sshd_config 找到第17行附近#Port 22 ...

  6. D3.js(v3)+react框架 基础部分之数据绑定及其工作过程与绑定顺序

    数据绑定: 将数据绑定到Dom上,是D3最大的特色.d3.select和d3.selectAll返回的元素的选择集.选择集上是没有数据的. 数据绑定就是使被选择元素里“含有”数据. 相关函数有两个: ...

  7. D17——C语言基础学PYTHON

    C语言基础学习PYTHON——基础学习D17 20181014内容纲要: 1.jQuery介绍 2.jQuery功能介绍 (1)jQuery的引入方式 (2)选择器 (3)筛选 (4)文本操作 (5) ...

  8. 优化openfire服务器提升xmpp 效率的15个方法(原创)

    1.禁用原生xmpp搜索,使组织架构.人员数据本地化保存,并使客户端数据同步服务器,降低原生xmpp搜索的iq消耗,因为搜索是im应用的频繁操作: 2.禁用roster花名册.禁用presence包通 ...

  9. 远程连接阿里云服务器出现"远程桌面,身份验证错误:要求的函数不受支持"解决办法

    ---恢复内容开始--- 更新:win10专业版用户可以看之前的的直接来,但家庭版用户用下面的好像并不能完美解决,献上在网上找到的一个终极解决办法 windows+R打开运行  输入regedit打开 ...

  10. [原创]K8 Struts2 Exp 20170310 S2-045(Struts2综合漏洞利用工具)

    工具: K8 Struts2 Exploit组织: K8搞基大队[K8team]作者: K8拉登哥哥博客: http://qqhack8.blog.163.com发布: 2014/7/31 10:24 ...