当前位置: 首页 > 后端技术 > Node.js

nodejs异步I-O与事件驱动

时间:2023-04-03 15:17:25 Node.js

异步IO(asynchronousI/O)阻塞I/O和非阻塞I/O阻塞I/O,即当用户发送读取文件描述符的操作,进程会一直阻塞,直到所有要读取的数据准备好返回给用户,这时进程才会释放阻塞状态。非阻塞I/O与上述情况相反。当用户发起读取文件描述符操作时,函数立即返回,无需任何等待,进程继续执行。但是程序怎么知道要读取的数据已经准备好了呢?最简单的方法是通过轮询。另外还有一种模式叫IO多路复用,就是用一个阻塞函数同时监听多个文件描述符。当其中一个文件描述符准备就绪时,它将立即返回。Linux下select、poll、epoll都提供了IO多路复用的功能。同步I/O和异步I/O那么同步I/O和异步I/O有什么区别呢?是不是只要实现了非阻塞IO就可以实现异步I/O呢?其实并不是。同步I/O(同步I/O)在做I/O操作时会阻塞进程,所以阻塞I/O、非阻塞I/O、IO多路复用I/O都是同步I/O。异步I/O(asynchronousI/O)在做I/O操作时不会造成任何阻塞。非阻塞I/O不阻塞,为什么异步I/O不行?前端训练其实当数据准备好进行非阻塞I/O时,进程仍然需要阻塞去内核去获取数据。所以它不是异步I/O。这是一张图片(图片来自这里)来说明它们之间的区别。Event-driven事件驱动(event-driven)是nodejs的第二大特点,就是通过监听事件的状态变化来做出相应的操作。比如读取文件时,文件读取完整,或者文件读取错误,则触发相应的状态,然后调用相应的回调函数进行处理。线程驱动和事件驱动线程驱动编程和事件驱动编程的区别:线程驱动是指当接收到一个请求时,会为该请求开启一个新的线程来处理该请求。一般都有一个线程池。线程池中有空闲线程,会从线程池中取出线程进行处理。如果线程池中没有空闲线程,则新的请求会进入队列并排队,直到线程池空闲为止。线。事件驱动是指当有新的请求进来时,会将请求推入队列,然后通过循环检测队列中的事件状态变化。如果检测到状态改变的事件,则相应的事件将被执行。处理代码一般是回调函数。对于事件驱动编程,如果某个时刻的回调函数是计算密集型的或者是阻塞I/O的,那么这个回调函数就会阻塞后续所有事件回调函数的执行。这一点尤其重要。nodejs的事件驱动和异步I/O事件驱动模型运行在单线程(singlethread)上,通过一个事件循环(event-loop)循环取出消息队列(event-queue)中的消息)进行处理。处理过程基本上就是调用消息对应的回调函数。消息队列就是当一个事件的状态发生变化时,将消息推入队列。nodejs的时间驱动模型一般要注意以下几点:1.因为是单线程,当js文件中的代码顺序执行时,事件循环被挂起。2.js文件执行时,事件循环开始运行,从消息队列中取出消息,回调函数开始执行。3.因为是单线程的,当执行回调函数时,事件循环被挂起。4.当涉及到I/O操作时,nodejs会开启一个独立的线程进行异步I/O操作,操作完成后将消息推入消息队列。话不多说,看例子varfs=require("fs");vardebug=require('debug')('example1');debug("begin");fs.readFile('package.json','utf-8',function(err,data){if(err)debug(err);elsedebug("getfilecontent");});setTimeout(function(){debug("timeout2");});调试('结束');//这里运行之前,事件循环被挂起,同步执行。debug("begin")异步调用fs.readFile()。此时会开启一个新的线程进行异步I/O操作,异步调用setTimeout(),立即将超时信息压入消息队列并同步调用debug("end")启动事件循环,弹出消息队列中的信息(当前超时信息),然后执行该信息对应的回调函数(事件循环和暂停)回调函数执行结束后,事件循环开始(目前消息队列中没有任何内容,并且该文件尚未被读取)。异步I/O读取文件后,将消息推入消息队列(消息中包含文件内容或者是错误消息),事件循环获取消息,执行回调程序,退出。这里用一张图来说明nodejs的事件驱动模型。这里最后要说的是如何手动将一个函数压入队列。Nodejs为我们提供了几个比较方便的方法:setTimeout()process.nextTick()setImmediate()异步I/OOnodejs中异步I/O的操作是通过libuv库实现的,libuv库包含了window下的异步I/O实现和Linux。