nodejs在处理http或者https请求的时候,是把和该请求有关的异步任务都执行完,才能处理下一个请求吗,这点挺困惑的,下面是我的代码实践。
在node里面模拟一个异步任务
const http = require('http');
const url = require('url');
http.createServer(function(req, res) {
// 革除/favicon.ico额外请求
if(url.parse(req.url).path == '/favicon.ico') {
return;
}
// 下面才是实践的主要内容
console.log("开始处理新请求了");
// 模拟5秒的长时间的异步任务
setTimeout(function() {
console.log('执行完异步任务了');
res.end('ok');
}, 5000)
}).listen(8080)
前端模拟在极短时间内有三个请求先后访问node服务器
for(var i = 0; i < 3; i++) {
window.open("http://localhost:8080/", "_blank");
}
下面是实际运行结果
结果显示node在处理请求的时候会把和该请求有关的异步代码执行完毕,才会处理下一个请求,不知道我的理解正不正确,请大家赐教,谢谢。
不要使用window.open进行测试 而使用postman类似的工具来测试 相关原因猜测应该是浏览器做了某些限制(针对同一url),你也可以用window.open(“http://localhost:8080/”+ i, “_blank”)看到效果
@zijin-m 用window.open(“http://localhost:8080/”+ i, “blank”)测试时,发现是我之前的理解错了,并不是要把异步代码执行完毕,才会处理下一个请求,感谢解答。 再追问一下,如果node主线程正在执行和上一个http请求有关的同步代码,如果这时又来了一个新的http请求,这时node主线程是继续执行之前的同步代码还是先去处理新的http请求呢?
@lichaosuper node是单线程的 所以主线程处理同步代码的时候 不会处理新的请求 这也是node不能做cpu密集(主线程耗时较长)的原因 个人理解~
@lichaosuper 你用不同浏览器请求,或者 用postman 就能看到真实情况了
@zijin-m 两码事
你这个测试工具有问题,用ab
同一浏览器发起多次请求,还是会被当做同一个用户,阻塞在那。 你用不同浏览器,或者不同电脑,就能看到这块非阻塞了。
关于测试 模拟并发请求可以使用postman等工具,for循环中依旧是顺序请求
关于服务端代码如何运行 涉及知识点:nodejs的event loop如何工作 程序员对于同步执行的代码会比较容易理解,因为这符合人的思维习惯,可是像nodejs这种异步的代码,就有点看不懂它具体是怎么执行的了。但咱们会遇到这样的问题,别人也会遇到,其中一个人就做了一个“模拟器”,很直观的展示nodejs异步代码执行的整个过程。为了让问题中的代码能在模拟器中执行,我稍微修改了一下代码(本质不变,不影响理解):
//每次请求到达8080端口,就会开始执行httpRequest这个function(也就是问题中createServer中的callback) function httpRequest(req, res){ console.log(‘start to process request.’); setTimeout(function(){ console.log(‘finish callback’); //res.end(‘ok’); }, 7000); //为了更加直观,适当调整一下时间 console.log(‘ready to accept another request.’); } //模拟并发请求(其实不是并发,但是不影响理解,真正的并发也是同样的道理) httpRequest({},{}); httpRequest({},{}); httpRequest({},{});
来,我们先感性的看下这段代码的执行过程 因为压缩的比较严重,如果看不清,可以访问最后参考资料中的模拟工具链接,自行运行。如果觉得程序运行过快,可以点击左上角的图标,然后调整’Detlay time’来控制程序执行的速度。
介绍一下该工具
- 左上角点击图标显示额外的设置界面。'delay time’用来调整代码执行的速度,'Simulate renders’用来模拟浏览器中的event loop和页面render的关系(nodejs的event loop和浏览器中的会有一些差异)
- 中间是代码部分。
- 右边’Call Stack’是栈,也就是楼主理解的运行同步代码的地方。 'Web Apis’表示所有异步的请求,比如http请求,IO等等,它们完成需要一些时间。 'Callback Queue’是一个存放各种callback的队列,比如web api中的某一个http请求完成了,那么对应的callback方法就会进到该队列。
相信到现在为止,大家应该对该代码的执行过程有所了解,其中有几个需要注意的点: 1.stack中的代码没执行完,Callback queue是不会出栈的。 2.截图中的’Callback queue’看起来就一个队列,其实真正的实现是多个队列,每个队列存放不同类型的callback,event loop基于某种顺序以及规则轮询这些队列,如果了解清楚这个,那么对于某些程序执行的结果就不会感到吃惊。
扩展思考 1.如果代码中出现CPU密集的任务,比如编码解码或者大循环,对程序会有什么影响?Nodejs如何处理CPU密集型的计算(提示:分片或partition,C++ addon,child process,cluster)? 2.如果一段代码中同时包含setTimeout,setInternal,Setimmediat,Process.nextTick,那么他们对应的Callback执行顺序是怎样的?其中Process.nextTick的使用需要注意什么?
参考资料 菲利普·罗伯茨:到底什么是Event Loop呢? nodejs event loop运行模拟工具 nodejs event loop 文档
@wxlfight 学习了,谢谢
@wxlfight 我使用nodejs event loop运行模拟工具执行了您的代码,发现node主线程在处理并发请求时,是逐个处理请求,执行和请求有关的同步代码,遇到异步事件就挂起,继续往后执行同步代码,异步事件有了结果就放到事件循环队列中去,node主线程如果处理完一个请求的同步代码,发现还有请求没处理,就继续去处理请求,先不去处理事件循环队列中的任务,等到所有请求都处理完,主线程空闲了,才会去执行事件循环队列中的任务,这也是node擅长I/O密集型,不擅长CPU密集型的原因,因为cpu密集型任务的话导致node主线程花费长时间在处理CPP密集型任务上,导致前一个请求(假设请求的返回是在异步任务里面)在事件循环的任务队列中没返回,而后一个请求又没空处理。不知这样理解正确不?
@zuohuadong 嗯嗯