浅析NodeJS非阻塞异步I/O |
您所在的位置:网站首页 › nodejs非阻塞io原理 › 浅析NodeJS非阻塞异步I/O |
前言: NodeJS是一个基于Chrome V8引擎的javascript运行时环境 Node使用: 单线程 非阻塞I/O 事件驱动的方式实现了高并发请求,libuv提供了异步编程的能力 1 几种常见的I/O目前现有的几种I/O模型 阻塞I/O 多线程阻塞I/O 非阻塞I/O I/O多路复用:select/poll/epoll 异步I/O阻塞I/O 阻塞I/O是read/write函数的默认执行机制,会在读写操作执行时将进程设置为阻塞态,I/O完成之后,系统中断将其更改为就绪状态,等待时间片分配,继续执行下一次I/O或者函数。 缺点: 无法并发执行I/O操作,无法同时在I/O操作执行的同时请求CPU计算,如果一个请求读取状态发生阻塞,那么其他请求无法处理 优点: 简单易懂,代码实现简单 适用于小型的命令行工具,简单程序 多线程阻塞I/O 有很多思路,这里学习一个 使用多线程 初始化一个线程池,利用信号量的 wait 原语进入阻塞状态。等到有 I/O 操作需求时,通过信号量signal将线程唤醒并执行相关的 I/O 操作 缺点:多线程非阻塞 I/O 有个弊端,就是当连接数达到很大的一个程度时,线程切换也是一笔不小的开销。 非阻塞I/O 非阻塞 I/O 是一种机制,允许用户在调用 I/O 读写函数后,立即返回,如果缓冲区不可读或不可写,直接返回 -1。 返回后就可以同时执行其他cpu的计算了。 缺点: 如果 while 循环轮询等待执行的操作,会造成不必要的 CPU 运算的浪费,因为此时 I/O 操作未完成,read 函数拿不到结果; 如果使用 sleep/usleep 的方式强行让进程睡眠一段时间,又回造成 I/O 操作的返回不及时。I/O多路复用 就是一个进程内同时执行多个I/O操作, 分为 select, poll, epoll(macos 上的替代品是 kqueue) 这几个阶段 select:select 作用是可以批量监听 fd,当传入的 fd_set 中任何一个 fd 的缓冲区进入可读/可写状态时,解除阻塞,并通过 FD_ISSET 来循环定位到具体的 fd select带来的问题:fd_set(集合)允许的最大数量是1024 ; 每次执行select函数都会存在fd_set的拷贝,轮训存在性能开销 解决上述问题需要poll出场: poll:poll函数将接受的fd集合更改为数组,就没有了1024的限制, 但是性能开销的问题还存在,由 epoll解决 epoll:epoll有三个阶段 epoll_create;epoll_ctl ; epoll_waite epoll_create在fd绑定阶段控制不需要传入重复fd集合 epoll_ctl 将传入的fd在内核态维护一颗红黑树,当I/O操作完成时,通过红黑树以 O(LogN) 的方式定位到 fd,避免轮询; epoll_waite:监听任意的fd发生变化之后解除阻塞 缺点:epoll 目前只支持 pipe, 网络等操作产生的 fd,暂不支持文件系统产生的 fd。 异步I/O 无论是阻塞 I/O 还是 非阻塞 I/O 还是 I/O 多路复用,都是同步 I/O。都需要用户等待 I/O操作完成,并接收返回的内容。而操作系统本身也提供了异步 I/O 的方案,对应到不同的操作系统: Linux aio,目前比较被诟病,比较大缺陷是只支持 Direct I/O(文件操作) io_uring, Linux Kernel 在 5.1 版本加入的新东西,被认为是 Linux 异步 I/O 的新归宿 windows iocp,作为 libuv 在 windows 之上的异步处理方案。(笔者对 windows 研究不多,不多做介绍了。)前面写了很多.这里来参考一个大佬的总结链接: 阻塞I/O: 在发起I/O操作之后会一直阻塞着进程,不执行其他操作,直到得到响应或者超时为止; 非阻塞I/O:发起I/O操作不等得到响应或者超时就立即返回,让进程继续执行其他操作,但是要通过轮询方式不断地去check数据是否已准备好 多路复用I/O:又分为select、pool、epool。最大优点就是单个进程就可以同时处理多个网络连接的IO。 基本原理就是select/poll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。 而epool通过callback回调通知机制.减少内存开销,不因并发量大而降低效率,linux下最高效率的I/O事件机制。 同步I/O:发起I/O操作之后会阻塞进程直到得到响应或者超时。前三者阻塞I/O,非阻塞I/O,多路复用I/O都属于同步I/O。 注意非阻塞I/O在数据从内核拷贝到用户进程时,进程仍然是阻塞的,所以还是属于同步I/O。 异步I/O:直接返回继续执行下一条语句,当I/O操作完成或数据返回时,以事件的形式通知执行IO操作的进程上面介绍了几种主流的I/O,下面开始将今天的重点 非阻塞I/O 2 为什么选择非阻塞异步I/O单线程: 前面提到 node是基于Chrome V8引擎的javascript运行时 注意两个关键点:Chrome javascript; 也就是说node的运行环境是浏览器 语言是javascript 1 javascript语言本身就是单线程的,优点是不会像多线程(java)语言在编程是出现线程同步,线程锁的问题,避免了上下文切换带来的性能开销问题。 2 在浏览器环境下只能使用单线程,否则多个线程对同时对DOM操作会乱套。 node定位: 再来看nodejs的定位:提供一种简单安全的方法在 JavaScript 中构建高性能和可扩展的网络应用程序。 传统的服务器语言大都是多线程。阻塞式I/O,在用户建立连接时,每个连接都是一个线程,十万个用户建立连接,就有十万个线程, node采用 非阻塞异步I/O,最大的优势就是性能强,同样的服务器性能如果使用node可以比传统服务器语言多容纳100倍的用户,(I/O操作越多,node的优势越明显),擅长I/O密集型任务,可以使用在一些I/O密集型的高并发场景中。所以node的定位也选择非阻塞异步I/O也更加适合 这里有个非常有意思的解释:【Node.js】如何理解非阻塞I/O(详解) 3 非阻塞异步I/O的实现实现异步非阻塞I/O需要做两件事情:1.实现非阻塞。2 实现异步。 首先分析一下非阻塞异步I/O的过程:在node执行了读取数据库的代码之后,将立即转为执行后面的代码,把读取数据返回的结果的处理代码放到回调函数中。当某个I/O执行完毕的时候,node用事件的形式通知执行I/O操作的线程,然后线程执行这个事件对应回调函数(中的处理函数) 对于一个网络IO 涉及到的两个系统为: 1 调用这个系统的进程或者线程; 2 系统内核 出现I/O操作时 两个阶段: 1 系统内核把数据准备好 2 将数据从系统内核中拷贝到用户进程中。 这里再来区分一下: 阻塞I/O和非阻塞I/O区别在于:在I/O操作的完成或数据的返回前是等待还是返回(可以理解成一直等还是分时间段等) 同步I/O和异步I/O区别在于 :在I/O操作的完成或数据的返回前会不会将进程阻塞(或者说是主动查询还是被动等待通知 有了上面的基础知识,我们继续学习node中是怎么实现非阻塞异步I/O的 实现非阻塞: 通过事件循环来实现。 事件循环的原理单独放一篇来讲,这里简述一下,每个node进程有一个主线程在执行代码, 同时维护一个事件队列,在遇到网络请求或者异步操作的时候,此操作会先放到事件队列中排队(不会马上执行),同时主线程的代码也会继续执行, 当主线程的代码执行完成之后,通过事件循环机制,检查队列中 是否有要处理的事件,然后从队列头中取出事件,并分配线程处理事件,全部执行完毕之后,事件队列通知主线程,执行回调,把线程归还给线程池。 实现异步 实现异步的基础也是事件循环: nodejs中的事件循环分为以下六个阶段,每种回到都会在对应的阶段:这里I/O的异步是在poll阶段,poll阶段组要处理的就是IO输入和输出是否就绪。 这里分为网络请求异步io和文件的异步io 网络请求的异步IO是在事件循环的基础上poll阶段完成的,前面我们提到poll函数将接受的fd集合更改为数组,是poll的特点, 其实select poll和eppll都有一个共同的特点:在io轮询阶段,会有一个timeOut时间。也就是轮询结束时间, 在io操作时,如果读取文件成功,会直接退出当前轮询,如果一直没有成功,那么在timeOUt时间到达后退出当前轮询。 但是存在一个问题 异步I/O只能存在linux系统中,且无法利用系统缓存,windows对此的解决方案是使用多线程 IOCP 这个系统API(其内部还是用线程池完成的)。 实现异步IO结论: 1 node调用异步方法 2 node核心模块创建对应的文件I/O观察者对象 3 根据不同平台(Linux或者window)在poll阶段,使用了操作系统内核中的io机制中timeout方式,实现异步 4 回调通知 文件的异步IO 调用的是libuv库的线程池 libuv 使用 epoll 来构建 event-loop 的主体,其中: socket, pipe 等能通过 epoll 方式监听的 fd 类型,通过 epoll_wait 的方式进行监听; 文件处理 / DNS 解析 / 解压、压缩等操作,使用工作线程的进行处理,将请求和结果通过两个队列建立联系,由一个 pipe 与主线程进行通信, epoll 监听该 fd 的方式来确定读取队列的时机过程拆解: 1 创建请求I/O请求对象(包含回调函数) 2 timeout结束或者获取到数据之后推入线程池,调用返回 3回调通知 参考链接: Node.js 异步非阻塞 I/O 机制剖析 Node.js理论实践之《异步非阻塞IO与事件循环》 阻塞对比非阻塞一览 nodejs是如何实现非阻塞异步io的? |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |