nodejs如何利用libuv实现事件循环和异步

2023-11-19

本文是根据之前在公司内部做的分享整理而成。是早期对nodejs的一个认识。源码版本10.x。

  1. nodejs是什么?
  2. libuv的工作原理
  3. nodejs的工作原理
  4. nodejs如何使用libuv实现事件循环和异步

1 nodejs是什么?

Nodejs本质上是对js功能的拓展。提供了网络、文件、dns解析、进程线程等功能。

1.1 Nodejs是如何拓展js功能的?
利用v8提供的接口。

1.2 如何在v8新建一个自定义的功能?

// c++里定义
Handle<FunctionTemplate> Test = FunctionTemplate::New(cb);    
global->Set(String::New(“Test"), Test);

// js里使用  
var test = new Test();

1.3 nodejs是如何实现拓展的
但nodejs不是给每个功能拓展一个对象,而是拓展一个process对象,再通过process.binding拓展js功能。Nodejs定义了一个js对象process,映射到一个c++对象process,底层维护了一个c++模块的链表,js通过调用js层的process.binding,访问到c++的process对象,从而访问c++模块(类似访问js的Object、Date等)。

2 libuv的工作原理

2.1 Libuv是什么?为什么nodejs需要他?
libuv是一个跨平台异步IO库。因为Nodejs是单线程的,作为服务器,他涉及到IO,而IO是会阻塞的,从而影响性能。所以Nodejs把IO操作交给libuv,保证主线程可以继续处理其他事情。Libuv做了什么?Libuv主要是,利用系统提供的事件驱动模块解决网络异步IO,利用线程池解决文件IO。另外还实现了定时器,对进程,线程等使用进行了封装。
1 新建一个uv_loop_t* loop。loop中保存了各个阶段对应的数据结构。

2 执行uv_run函数进入“死”循环。

3 用户(nodejs)操作loop里的结构,注册事件和回调。

4 libuv在每一轮循环里处理各个阶段。

2.2 libuv的各个阶段(phase)
1 定时器(setTimeout)

2 pending callback

3 idle(自定义)

4 prepare(自定义)

5 poll i/o(网络和文件IO)

6 check(setImmediate)

7 close callback(关闭一个handle)

2.3 libuv的实现
最小堆(定时器)

链表(check、idle等)

线程池(文件io)

操作系统提供的事件驱动模块(网络io)

3 Nodejs的启动流程

1 注册内置c++模块(通过process.binding函数使用内置c++模块)。

2 新建process的c++对象,设置一系列属性(binding),然后挂载到全局。

3 执行bootstrap_node.js,初始化和挂载nextTick,setTimeout等函数,然后加载用户js,编译执行。

4 调用libuv开始事件循环。

3.1 注册内置c++模块
1 每个c++模块由一个node_module结构体管理。

2 用链表的方式把各个模块的node_module连接起来。

3 运行时,js通过process.binding函数从链表中找到对应的模块,从而使用c++模块功能。

3.2 process对象的生成和作用
1 新建一个c++的process对象

// 利用v8新建一个函数  
auto process_template = FunctionTemplate::New(isolate()); 
// 设置函数名  
process_template->SetClassName(FIXED_ONE_BYTE_STRING(isolate(), "process"));  
// 利用函数new一个对象  auto process_object =      process_template->GetFunction()->NewInstance(context()).ToLocalChecked(); 
 // 设置env的一个属性,类型是Object,val是process_object  
set_process_object(process_object);

2 nodejs如何访问global?

通过给global增加一个global属性

Local<Object> global = env->context()->Global();    
global->Set("global", global);

3 nodejs如果设置process对象?

编译node_bootstrap.js成c++代码,执行时传入c++的process对象,执行global.process = process; 从js层面来看,是多了一个全局变量process

4 process的Binding函数

process.Binding加载c++模块。实现js使用c++模块功能。const { TCP } = process.binding(‘tcp_wrap’); const tcp = new TCP();tcp.listen();js里通过process.binding加载一个c++模块的时候,这段js在编译后执行,首先访问js层的process对象,v8知道js的process对象对应是c++的process对象,再通过底层的Binding,就可以使用c++模块的功能了。

3.3 执行bootstrap_node.js
1 挂载全局变量setTimeout,Buffer等,给process对象挂载nexTick函数等初始化工作。

2 执行用户js

3.4 调用libuv开始事件循环。

4 nodejs如何利用libuv实现异步和事件循环?

如何生成任务给事件循环系统消费?

1 setTimeout

2 setImmediate

3 文件io

4 网络io

4.1 setTimeout的实现

1 用户调用setTimeout,设置时间是s秒

2 找到s对应的链表L。

3 如果L不存在则新建一个,并在libuv最小堆里新增一个超时节点。

4 往链表L头部插入一个Timeout节点。返回。(最早超时在链表末尾)

5 uv_run执行uv__run_timers判断是否有超时节点。

6 从后往前遍历链表L,如果当前节点没有超时则全部没有超时,设置新的超时时间,否则执行超时回调。

4.2 setImmediate实现
1 nodejs启动的时候注册了check阶段的一个c++层回调是CheckImmediate,该函数再执行js回调processImmediate

2 用户调用setImmediate,生成一个节点插到双向链表。返回。

3 uv_run在check阶段。执行回调。setImmediate和setTimeout的关系这两个其实没什么关系,对应的阶段也不一样。

4.3 文件io
为啥用线程池实现文件操作的异步?
因为文件的异步操作在各操作系统中兼容性不好。libuv线程池默认打开4个,最多打开128个线程。所有线程共享一个任务队列,当有任务的时候,添加到任务队列,线程的工作函数在死循环里不断处理队列里的任务。Libuv初始化的时候,注册了一个异步的io观察者A,用于子线程和主线程间通信的。io观察者A设置了一个管道文件描述符和回调。子线程完成任务后设置该任务的标记位,然后通过管道通知主线程,主线程在uv_run的poll io阶段会执行观察者A的回调,观察者的回调会判断每个异步任务的状态。然后执行用户的回调。

文件操作的过程
1 打开一个文件,新建一个c++ FSReqWrap对象。设置用户回调。调用FSReqWrap对象的Open,接着调用libuv层uv_fs_open。uv_fs_open。Libuv生成一个任务放到线程池的任务队列,返回nodejs。Nodejs可以继续做其他事情。

2 线程池处理该任务,线程会阻塞直到任务完成。比如读写文件,dns查询,然后设置任务的完成标记,可以通过管道写端通知主线程。主线程执行c++层回调,再执行js层回调。

4.4 网络io
网络io的实现方案。利用操作系统提供的事件驱动模块。

var http = require('http'); 
http.createServer(function (request, response) { response.end('Hello World\n'); }).listen(9297);

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

nodejs如何利用libuv实现事件循环和异步 的相关文章

随机推荐

  • 面试题六道-2022-1-6

    CopyOnWriteArrayList的底层原理是怎样的 1 首先CopyOnWriteArraylist内部也是用过数组来实现的 在向CopyOnWriteArrayLlist添加元素时 会复制一个新的数组 写操作在新数组上进行 读操作
  • 尚筹网-前台-会员系统(springboot,springcloud 实战)

    总目标 环境搭建 会员登录注册 发起众筹项目 展示众筹项目 支持众筹项目 订单 支付 1 会员系统架构 1 1 架构图 1 2 需要创建的工程 父工程 聚合工程 shangcouwang01 member parent 唯一的pom工程 注
  • 强化学习 9 —— DQN 改进算法 DDQN、Dueling DQN 详解与tensorflow 2.0实现

    上篇文章强化学习 详解 DQN 算法介绍了 DQN 算法 但是 DQN 还存在一些问题 本篇文章介绍针对 DQN 的问题的改进算法 一 Double DQN 算法 1 算法介绍 DQN的问题有 目标 Q 值 Q Target 计算是否准确
  • VUE常用UI组件插件及框架-vue前端UI框架收集

    UI组件及框架 element 饿了么出品的Vue2的web UI工具套件 mint ui Vue 2的移动UI元素 iview 基于 Vuejs 的开源 UI 组件库 Keen UI 轻量级的基本UI组件合集 vue material 通
  • Java jar包启动及停止

    此处以SpringBoot maven工程为基础 基于Windows系统 工程开发好后 打包成jar包 一 启动jar包 在包所在的目录下运行cmd命令 执行命令 java jar jar包名 二 停止 1 用管理员打开cmd命令窗口 2
  • Java安全之SSL/TLS

    在前面所讲到的一些安全技术手段如 消息摘要 加解密算法 数字签名和数据证书等 一般都不会由开发者直接地去使用 而是经过了一定的封装 甚至形成了某些安全协议 再暴露出一定的接口来供开发者使用 因为直接使用这些安全手段 对开发者的学习成本太高
  • 初学树莓派——(六)树莓派安装OpenCV及USB摄像头配置

    目录 1 安装OpenCV 1 1前言 1 2换源及源内容更新 1 3安装依赖 1 4下载whl包 1 5安装OpenCV 1 6检查安装 2 USB摄像头配置 同时检查OpenCV安装情况 2 1前言 2 2Python调用cv2库来检查
  • sem_init函数用法

    sem init函数 sem init函数是Posix信号量操作中的函数 sem init 初始化一个定位在 sem 的匿名信号量 value 参数指定信号量的初始值 pshared 参数指明信号量是由进程内线程共享 还是由进程之间共享 如
  • 最优化算法概述以及常见分类

    1 最优化问题概述 通俗的来说 最优化问题就是在一定的条件约束下 使得效果最好 最优化问题是一种数学问题 是研究在给定的约束之下如何求得某些因素的量 来使得某一指标达到最优的学科 工程设计中最优化问题的一般说法是 选择一组参数 在满足一系列
  • 数据结构笔记(六)——散列(Hash Table)之散列函数(1)

    散列表 hash table 的实现叫做散列 hashing 这是以常数平均时间O 1 进行插入 删除和查找的技术 散列表没有顺序 需要元素间排序信息的操作 如findMin findMax不会得到有效支持 就是这东西不是这么用的 你可以实
  • RocketMq顺序发送消息

    错乱消息出现的原因 1 在RocketMq为啥消息不是按照顺序来的呢 首先您需要了解 队列是一个先进先出的一个数据的结构 生产者 您可以将topic理解为里面有一个一个的队列 你将一个消息发送到topic的时候 当前的消息不一定是往当前的这
  • win 10 搭建FTP服务,并使用的FTP进行传输文件(很详细)

    1 安装IIS工具 打开控制面板 点击 程序 点击 启用或关闭Windows功能 找到 internet information services 全部都选上 如下图 点击 确定 会出现以下页面 点击 关闭 即可 2 设置开机启动FTP服务
  • 高光谱图像中的Hughes(休斯)现象

    注解 在高光谱图像的分析中 随着参与运算波段数目的增加 分类精度 先增后降 的现象 场景 高光谱影像 由于维数的大幅度增加 在深度学习中 可以理解成模型提取的特征维数的增加 导致用于参数训练的所需样本数也急剧增加 如果样本数过少 那么估计出
  • Fiddler 详尽教程与抓取移动端数据包

    转载自 http blog csdn net qq 21445563 article details 51017605 阅读目录 1 Fiddler 抓包简介 1 字段说明 2 Statistics 请求的性能数据分析 3 Inspecto
  • C++面试题目集合(持续跟新)

    与我前面写的C语言进阶知识点遥相呼应 这才是C 面试 网上的面试题有些太简单了 C 面试题目最多集中在对象的内存模型 记住了 如果用c c 内存都不清楚 还写个屁的程序 1 C 的虚函数是怎样实现的 C 的虚函数使用了一个虚函数表来存放了每
  • 我的世界服务器物品不掉落指令是什么,我的世界死亡物品怎么不掉落 我的世界物品不掉落指令...

    我的世界死亡不了多指令是gamerulekeepInventorytrue 玩家们要注意我的世界死亡不掉落指令默认是关闭状态的哦 死亡不掉落指令在 我的世界 游戏里面就是当玩家们死亡以后仍然保留其物品栏中的所有物品 包括附魔死亡消失魔咒的物
  • 安装WSL + zsh & Pure (ZSH prompt) 美化【Windows11】

    文章目录 前言 WSL 安装 ZSH 安装ZSH Pure ZSH prompt 安装插件 下载插件 编辑配置文件 插件作用 啊 PS 如果在启动过程中提示 请启用虚拟机平台 windows 功能并确保在 bios 中启用虚拟化 前言 之前
  • 数据分析36计(28):Python 使用 Flask+Docker, 100行代码内实现机器学习实时预测​...

    本文的想法是快速轻松地构建 Docker 容器 Python 以使用 Flask 实现机器学习模型执行在线预测 API 我们将使用 Docker 和 Flask RESTful 实现线性判别分析和多层感知器神经网络模型的实时预测 项目包括的
  • Android中的自绘View的那些事儿(八)之 Paint的高级用法

    我们在 Android中的自绘View的那些事儿 一 中简单介绍过Paint和Canvas的一些常用方法和实例使用 其中 一句话提到Paint中有方法 setStrokeCap setStrokeJoin 和 setPathEffect 今
  • nodejs如何利用libuv实现事件循环和异步

    本文是根据之前在公司内部做的分享整理而成 是早期对nodejs的一个认识 源码版本10 x nodejs是什么 libuv的工作原理 nodejs的工作原理 nodejs如何使用libuv实现事件循环和异步 1 nodejs是什么 Node