同步与异步
一谈起Node,了解过的人会想起是利用异步IO实现高性能后台,那么什么是异步IO呢?
异步IO与同步IO的区别是什么呢?举个例子:在生活中,大家每天都会用到微信来进行聊天。那么某个时间有两个人来跟你聊天,同步与异步会有不同的表现(单线程的情况下)。
- 同步:同步的做法是先选择一个比较先与你聊天的,把整个天都给聊完了,最后确定对方与你没有什么好聊的。ok接下去跟另外一个人进行聊天。
- 异步:异步的做法是选择同时和这两个人进行聊天,每一次把信息发出去,等待对方回信息的时间内你都可以做其他事情,比如与编辑回复另外人信息。
这两个就是同步与异步的区别。只要不傻,肯定都是以第二种方式进行聊天。很多人会想,我为什么要在这里讲同步于异步的区别,既然异步这么好,开发的语言应该用到的是异步吧?
其实不然,很多开发的语言用到是同步。java处理并发是用到多线程(虽然java有一个nio处理异步,但是之前是很少人用到的,现在应该逐渐有人去重视这个),每个线程的执行是同步的。如果在这个线程中进行读取磁盘文件,在读取的过程中这个线程的资源是没有利用的。这个是同步的缺点。那为什么不使用异步呢?!异步的开发成本要高于同步,一般执行顺序不符合正常的逻辑思维(一般不是从上到下)。先说这么多,后面再讲明原因。
异步I/O
关于异步I/O,向前端开发工程师讲起来会比较简单一点,因为前端页面可以理解为是基于事件驱动的。用ajax发起请求对于前端工程师来说是更熟悉不过的
$.post('/url', {
title: '深入浅出Node.js'}, function (data) {
console.log('收到响应');
});
console.log('发送Ajax结束');
执行这一段代码的时候,假设后台是响应成功的,执行顺序是先打印“发起Ajax结束”然后再“收到响应”。这个很好理解,执行第二次打印的时候是当后台返回数据说响应成功后执行的。
我们只知道它将在这个异步请求结束后执行,但并不知道具体的时间点。异步调用中对于结果值的捕获是符合“Don’t call me, I will call you”的原则的,这也是注重结果,不关心过程的一种表现。
下面是ajax请求时候的图解。
那么异步IO处理文件读取是怎么一回事呢?
Node调用文件读取需要导入fs模块,在浏览器中,js是无法直接访问主机上的文件的,是由于安全方面的考虑。但是node上是可以的。其代码如下:
const fs = require('fs');
fs.readFile('文件路径', (err, file) => {
console.log('文件读取完毕');
});
console.log('发起文件读取');
同样,这里发起文件读取是执行在文件读取完毕之前的。跟ajax类似,其图解也是类似的。
以上代码如果同时读取文件A和文件B,会几乎同时对A和B发起读取,然后线程可以执行其他代码,待到AB读取回调的时候,在执行相应的回调。这样就是异步IO的好处,可以原本读取一个文件的时间内读取更多的文件(假设文件同样大小)。
Node在这方面做得不错决定了Node比其他大多数后端语言的优势在于对IO密集型业务会有更好的表现。通常,说Node擅长I/O密集型的应用场景基本上是没人反对的。Node面向网络且擅长并行I/O,能够有效地组织起更多的硬件资源,从而提供更多好的服务。
I/O密集的优势主要在于Node利用事件循环的处理能力,而不是启动每一个线程为每一个请求服务,资源占用极少。
由于JavaScript是单线程语言,有人会问Node在cpu密集型业务逻辑中是不是表现不是很好。在JavaScript层面上讲,是对的。
换一个角度,在CPU密集的应用场景中,Node是否能胜任呢?实际上,V8的执行效率是十分高的。单以执行效率来做评判,V8的执行效率是毋庸置疑的。
这里引用《深入浅出nodejs》的一个小实验来进行。
那里用斐波那契数列用各种脚本语言进行试验,其中用java和go语言作为参考,得出结果如下图:
可以看出Node中的C++模块表现还是挺不错的。
分析如下:
这样的测试结果尽管不能完全反映出各个语言的性能优劣,但已经可以表明Node在性能上不俗的表现。从另一个角度来说,这可以表明CPU密集型应用其实并不可怕。CPU密集型应用给Node带来的挑战主要是:由于JavaScript单线程的原因,如果有长时间运行的计算(比如大循环),将会导致CPU时间片不能释放,使得后续I/O无法发起。但是适当调整和分解大型运算任务为多个小任务,使得运算能够适时释放,不阻塞I/O调用的发起,这样既可同时享受到并行异步I/O的好处,又能充分利用CPU。
关于CPU密集型应用,Node的异步I/O已经解决了在单线程上CP