浅析libuv源码-node事件轮询解析(4)
这篇应该能结,简图如下。

上一篇讲到了uv__work_submit方法,接着写了。
void uv__work_submit(uv_loop_t* loop,
struct uv__work* w,
enum uv__work_kind kind,
void (*work)(struct uv__work* w),
void (*done)(struct uv__work* w, int status)) {
// 上篇主要讲的这里 初始化线程池等
uv_once(&once, init_once);
w->loop = loop;
w->work = work;
w->done = done;
post(&w->wq, kind);
}
从post开始。
static void post(QUEUE* q, enum uv__work_kind kind) {
// 因为存在队列插入操作 需要加锁
uv_mutex_lock(&mutex);
if (kind == UV__WORK_SLOW_IO) {
//跳...
}
QUEUE_INSERT_TAIL(&wq, q);
// 如果有空闲线程 唤醒
if (idle_threads > )
uv_cond_signal(&cond);
uv_mutex_unlock(&mutex);
}
wq就是上一篇讲的线程都会用到的那个队列,这里负责插入任务,worker中取出任务。
没想到post到这里没了,这点东西并到上一篇就好了。以后写这种系列博客还是先规划一下,不能边看源码边写……
函数到这里就断了,看似没有线索,实际上在上一节的worker方法中,还漏了一个地方。
static void worker(void* arg) {
// ...
for (;;) {
// 这里调用内部fs方法处理任务
w = QUEUE_DATA(q, struct uv__work, wq);
w->work(w);
uv_mutex_lock(&w->loop->wq_mutex);
w->work = NULL;
QUEUE_INSERT_TAIL(&w->loop->wq, &w->wq);
// 这个是漏了的关键
uv_async_send(&w->loop->wq_async);
uv_mutex_unlock(&w->loop->wq_mutex);
// ...
}
}
每一条线程在每次处理完一条事务并将其插入工作队列wq后,都会调用一下这个uv_async_send方法,上一篇没讲这个。
这里的wq_async是一个在loop上面的变量,在轮询初始化的时候出现过,这里先不看。
uv_async_send这个方法又涉及到另外一个大模块,如下。
int uv_async_send(uv_async_t* handle) {
// 错误处理...
if (!uv__atomic_exchange_set(&handle->async_sent)) {
POST_COMPLETION_FOR_REQ(loop, &handle->async_req);
}
return ;
}
// 将操作结果推到iocp上面
#define POST_COMPLETION_FOR_REQ(loop, req) \
if (!PostQueuedCompletionStatus((loop)->iocp, \
, \
, \
&((req)->u.io.overlapped))) { \
uv_fatal_error(GetLastError(), "PostQueuedCompletionStatus"); \
}
这个地方说实话我并不是明白windows底层API的操作原理,IOCP这部分我没有去研究,只能从字面上去理解。
关于PostXXX方法官网解释如下:
Posts an I/O completion packet to an I/O completion port.
将一个I/O完成的数据打包到I/O完成的端口,翻译过来就是这样,个人理解上的话大概是把一个async_req丢到IOCP那里保存起来。
接下来终于可以回到事件轮询部分,点题了。
int uv_run(uv_loop_t *loop, uv_run_mode mode) {
// ...
while (r != && loop->stop_flag == ) {
// ...
// call pending callbacks
ran_pending = uv_process_reqs(loop);
// ...
// poll for I/O
if (pGetQueuedCompletionStatusEx)
uv__poll(loop, timeout);
else
uv__poll_wine(loop, timeout);
// ...
}
// ...
}
截取了剩下的poll for I/O、call pending callback,也就是剩下的两部分了。if判断不用管,只是一个方法兼容,最终的目的是一样的。
所以只看uv__poll部分。
static void uv__poll(uv_loop_t* loop, DWORD timeout) {
// ...
// 设定阻塞时间
uint64_t timeout_time;
timeout_time = loop->time + timeout;
for (repeat = ; ; repeat++) {
success = GetQueuedCompletionStatusEx(loop->iocp,
overlappeds,
ARRAY_SIZE(overlappeds),
&count,
timeout,
FALSE);
if (success) {
for (i = ; i < count; i++) {
if (overlappeds[i].lpOverlapped) {
req = uv_overlapped_to_req(overlappeds[i].lpOverlapped);
uv_insert_pending_req(loop, req);
}
}
uv_update_time(loop);
} else if (GetLastError() != WAIT_TIMEOUT) {
// ...
} else if (timeout > ) {
// 超时处理...
}
break;
}
}
这里的GetQueueXXX方法与之前的PostQueueXXX正好是一对方法,都是基于IOCP,一个是存储,一个是取出。
遍历操作就很容易懂了,取出数据后,一个个的塞到pending callback的队列中。
把uv_insert_pending_req、uv_process_reqs两个方法结合起来看。
INLINE static void uv_insert_pending_req(uv_loop_t* loop, uv_req_t* req) {
req->next_req = NULL;
// 插入到pending_reqs_tail上
if (loop->pending_reqs_tail) {
// DEBUG...
req->next_req = loop->pending_reqs_tail->next_req;
loop->pending_reqs_tail->next_req = req;
loop->pending_reqs_tail = req;
} else {
req->next_req = req;
loop->pending_reqs_tail = req;
}
}
INLINE static int uv_process_reqs(uv_loop_t* loop) {
// ...
// 处理pending_reqs_tail
first = loop->pending_reqs_tail->next_req;
next = first;
loop->pending_reqs_tail = NULL;
while (next != NULL) {
req = next;
next = req->next_req != first ? req->next_req : NULL;
switch (req->type) {
// handle各类req...
}
}
return ;
}
就这样,完美的把poll for I/O与call pending callback两块内容连接到了一起,也同时理解了一个异步I/O操作是如何在node内部被处理的。
最后还是剩一个尾巴,就是丢到IOCP的那个async_req怎么回事?这个变量在轮询的初始化方法中出现,如下。
typedef struct uv_loop_s uv_loop_t;
struct uv_loop_s {
// ...
UV_LOOP_PRIVATE_FIELDS
};
#define UV_LOOP_PRIVATE_FIELDS \
// 其余变量
uv_async_t wq_async;
// uv__word_done是这个handle的回调函数
int uv_loop_init(uv_loop_t* loop) {
// ...
err = uv_async_init(loop, &loop->wq_async, uv__work_done);
// ...
}
// 第一篇中演示过handle的初始化和运行 很常规的init、start两步
int uv_async_init(uv_loop_t* loop, uv_async_t* handle, uv_async_cb async_cb) {
uv_req_t* req;
uv__handle_init(loop, (uv_handle_t*) handle, UV_ASYNC);
handle->async_sent = ;
handle->async_cb = async_cb;
req = &handle->async_req;
UV_REQ_INIT(req, UV_WAKEUP);
req->data = handle;
uv__handle_start(handle);
return ;
}
# define UV_REQ_INIT(req, typ) \
do { \
(req)->type = (typ); \
} \
while ()
从代码里面可以知道,loop上本身带有一个uv_async_t的变量wq_async,初始化后有四个属性。其中需要注意,这个类型的type被设置为UV_WAKEUP。
再回到uv_process_reqs中,处理从IOCP取出的req那块。
INLINE static int uv_process_reqs(uv_loop_t* loop) {
// ...
while (next != NULL) {
// ...
switch (req->type) {
// ...
case UV_WAKEUP:
uv_process_async_wakeup_req(loop, (uv_async_t*) req->data, req);
break;
// ...
}
}
return ;
}
我们找到了处理UV_WAKEUP的case,参数参考上面那个初始化的代码也很容易得知,req->data就是loop初始化的那个handle,req是那个async_req。
方法代码如下。
void uv_process_async_wakeup_req(uv_loop_t* loop, uv_async_t* handle, uv_req_t* req) {
// 丢进IOCP的时候被设置为1了 具体在uv_async_send的uv__atomic_exchange_set方法中
handle->async_sent = ;
if (handle->flags & UV_HANDLE_CLOSING) {
uv_want_endgame(loop, (uv_handle_t*)handle);
} else if (handle->async_cb != NULL) {
// 进的else分支
handle->async_cb(handle);
}
}
这里的async_cb也是初始化就定义了,实际函数名是uv__work_done。
void uv__work_done(uv_async_t* handle) {
// ...
loop = container_of(handle, uv_loop_t, wq_async);
uv_mutex_lock(&loop->wq_mutex);
// 还是那个熟悉的队列
QUEUE_MOVE(&loop->wq, &wq);
uv_mutex_unlock(&loop->wq_mutex);
while (!QUEUE_EMPTY(&wq)) {
// ...
w->done(w, err);
}
}
这个done,就是用户从JS传过去的callback……
也就是说call pending callback实际上是调用用户传过来的callback,第二篇的图其实是有问题的,系列完结撒花!
浅析libuv源码-node事件轮询解析(4)的更多相关文章
- 浅析libuv源码-node事件轮询解析(3)
好像博客有观众,那每一篇都画个图吧! 本节简图如下. 上一篇其实啥也没讲,不过node本身就是这么复杂,走流程就要走全套.就像曾经看webpack源码,读了300行代码最后就为了取package.js ...
- 浅析libuv源码-node事件轮询解析(2)
上一篇讲了轮询的边角料,这篇进入正题.(竟然真有人看我博客,上两个图给你们整理下思路) 这是轮询总流程图. 下图为本节内容简图. Poll for I/O The loop blocks for I/ ...
- 浅析libuv源码-node事件轮询解析(1)
好久没写东西了,过了一段咸鱼生活,无意中想起了脉脉上面一句话: 始终保持自己的竞争力.所以,继续开写! 一般的JavaScript源码看的已经没啥意思了,我也不会写什么xx入门新手教程,最终决定还是啃 ...
- 浅析libuv源码-编译启动
面试的间隙回头复习了一下node,感觉node就像一个胶带,把V8和libuv粘在了一起. V8毫无疑问,负责解析执行JavaScript,相当于语言层面的桥梁:而libuv则是负责操作系统底层功能的 ...
- 浅析libuv源码-获取精确时间
在Timer模块中有提到,libuv控制着延迟事件的触发,那么必须想办法精确控制时间. 如果是JS,获取当前时间可以直接通过Date.now()得到一个时间戳,然后将两段时间戳相减得到时间差.一般情况 ...
- node.js事件轮询(1)
事件轮询(引用) 事件轮询是node的核心内容.一个系统(或者说一个程序)中必须至少包含一个大的循环结构(我称之为"泵"),它是维持系统持续运行的前提.nodejs中一样包含这样的 ...
- 理解Node.js的事件轮询
前言 总括 : 原文地址:理解Node.js的事件轮询 Node小应用:Node-sample 智者阅读群书,亦阅历人生 正文 Node.js的两个基本概念 Node.js的第一个基本概念就是I/O操 ...
- 对Node.JS的事件轮询(Event Loop)的理解
title: Node.JS的事件轮询(event loop)的理解 categories: 理解 tags: Node JS 机制 当我们知道I/O操作和创建新线程的开销是巨大的! 网站延迟的开销 ...
- Node.js的异步IO和事件轮询
想象一下,以前我们在写程序时, 如果程序在I/O上阻塞了,当有更多请求过来时,服务器会怎么处理呢?在这种情景中通常会用多线程的方式.一种常见的实现是给每个连接分配一个线程,并为那些连接设置一个线程池 ...
随机推荐
- Spring Cloud Eureka详细说明
之前学习了如何配置Eureka注册中心.消费者等,关于更详细的一些常用的配置在这里说明. 1.注册中心的自我保护模式 在我们调试Eureka的注册中心时,访问注册中心页面,常常会看见以下提示. 该提示 ...
- 打开前端工程 Node Sass does not yet support your current environment: Windows 64-bit
卸载当前sass版本,重新安装sass 打开cmd进入工程文件夹: 删除 npm uninstall --save node-sass 安装 npm install --save node-sass ...
- SQL注入:盲注
盲注简介 所谓的盲注就是在服务器没有错误回显的时候完成的注入攻击. 服务器没有错误回显,对于攻击者来说缺少了非常重要的"调试信息". 盲注分类 1.布尔盲注 布尔很明显Ture和F ...
- SpringBoot整合easyexcel实现Excel的导入与导出
导出 在一般不管大的或者小的系统中,各家的产品都一样,闲的无聊的时候都喜欢让我们这些程序员导出一些数据出来供他观赏,非说这是必须需求,非做不可,那么我们就只能苦逼的哼哧哼哧的写bug喽. 之前使用PO ...
- 201871010111-刘佳华《面向对象程序设计(java)》第二周学习总结
201871010111-刘佳华<面向对象程序设计(java)>第二周学习总结 项目 内容 这个作业属于哪个课程 <https://www.cnblogs.com/nwnu-daiz ...
- 283.移动零 关于列表list与remove原理*****(简单)
题目: 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序. 注意,该题目要求不开辟行的数组空间,在原数据上进行操作. 示例: 输入: [0,1,0,3,12 ...
- maven下载与配置(三)
一.下载 在官网下载 https://maven.apache.org/download.cgi 我这里现在的是apache-maven-3.2.5 . 二.配置环境变量 右键我的电脑-属性-高级系统 ...
- http的get请求与post请求区别
原文 http://www.w3school.com.cn/tags/html_ref_httpmethods.asp GET 方法 请注意,查询字符串(名称/值对)是在 GET 请求的 URL 中发 ...
- WordCount-JAVA版
WordCountMapper import java.io.IOException; import org.apache.hadoop.io.IntWritable; import org.apac ...
- LG1155 「NOIP2008」双栈排序 二分图判定
问题描述 LG1155 题解 \(i,j\)如果不能进入一个栈,要满足存在\(k\),使得\(i<j<k\)且\(a_k<a_i<a_j\) 如果\(i,j\)不能进入一个栈, ...