Node.js 是单线程的。因此,只要您有一个长时间运行的例程,它就无法处理其他代码段。在这种情况下,有问题的代码是你的双 for 循环,它占用了大量的 CPU 时间。
为了了解您首先看到的内容,让我解释一下事件循环的工作原理。
Node.js 事件循环是从 javascript 的事件循环演变而来的,而 javascript 的事件循环又是从 Web 浏览器事件循环演变而来的。 Web 浏览器事件循环最初不是为 javascript 实现的,而是允许渐进式渲染图像。事件循环看起来有点像这样:
,-> is there anything from the network?
| | |
| no yes
| | |
| | '-----------> read network data
| V |
| does the DOM need updating? <-------------'
| | |
| no yes
| | |
| | v
| | update the DOM
| | |
'------'--------------'
添加 javascript 后,脚本处理被简单地插入到事件循环中:
,-> is there anything from the network?
| | |
| no yes
| | |
| | '-----------> read network data
| V |
| any javascript to run? <------------------'
| | |
| no yes
| | '-----------> run javascript
| V |
| does the DOM need updating? <-------------'
| | |
| no yes
| | |
| | v
| | update the DOM
| | |
'------'--------------'
当 javascript 引擎在浏览器之外运行时,如在 Node.js 中,DOM 相关部分被简单地删除,I/O 变得通用:
,-> any javascript to run?
| | |
| no yes
| | |
| | '--------> RUN JAVASCRIPT
| V |
| is there any I/O <------------'
| | |
| no yes
| | |
| | v
| | read I/O
| | |
'------'--------------'
请注意,您的所有 javascript 代码都在 RUN JAVASCRIPT 部分执行。
那么,当您建立 10 个连接时,您的代码会发生什么情况?
connection1: node accepts your request, processes the double for loops
connection2: node is still processing the for loops, the request gets queued
connection3: node is still processing the for loops, the request gets queued
(at some point the for loop for connection 1 finishes)
node notices that connection2 is queued so connection2 gets accepted,
process the double for loops
...
connection10: node is still processing the for loops, the request gets queued
(at this point node is still busy processing some other for loop,
probably for connection 7 or something)
request1: node is still processing the for loops, the request gets queued
request2: node is still processing the for loops, the request gets queued
(at some point all connections for loops finishes)
node notices that response from request1 is queued so request1 gets processed,
console.log gets printed and res.send('over') gets executed.
...
request10: node is busy processing some other request, request10 gets queued
(at some point request10 gets executed)
这就是为什么您看到节点需要 10 秒来回答 10 个请求的原因。并不是请求本身很慢,而是它们的响应排在所有 for 循环之后,并且 for 循环首先执行(因为我们仍在事件循环的当前循环中)。
为了解决这个问题,您应该使 for 循环异步,让节点有机会处理事件循环。您可以用 C 编写它们并使用 C 为它们中的每一个运行独立的线程。或者,您可以使用 npm 中的线程模块之一在单独的线程中运行 javascript。或者你可以使用 worker-threads,它是一个为 Node.js 实现的类似于 API 的 web-worker。或者您可以分叉一组进程来执行它们。或者,如果并行性不重要,您可以简单地使用 setTimeout 循环它们:
router.use('/test/:id', function (req, res) {
var id = req.param('id');
console.log('start cpu code for ' + id);
function async_loop (count, callback, done_callback) {
if (count) {
callback();
setTimeout(function(){async_loop(count-1, callback)},1);
}
else if (done_callback) {
done_callback();
}
}
var outer_loop_done=0;
var x2=0;
async_loop(10000,function(){
x1++;
async_loop(30000,function(){
x2++;
},function() {
if (outer_loop_done) {
console.log('cpu code over for ' + id);
request('http://terranotifier.duapp.com/wait3sec/' + id,
function (a,b,data){
console.log('IO over for ' + data);
res.send('over');
}
);
}
});
},function(){
outer_loop_done = 1;
});
});
上述代码将尽快处理来自request() 的响应,而不是等待所有async_loops 执行完成而不使用线程(因此没有并行性),而只是使用事件队列优先级。