我刚刚意识到(单线程)Node.js的问题:
服务器开始响应请求,请求一直运行,直到它因I / O而阻塞。
当请求处理器阻止时,服务器启动并返回步骤#1,处理更多请求。
每当请求处理器阻止I / O时,服务器都会检查是否有任何请求完成。它以FIFO顺序处理那些以响应客户端,然后继续像以前一样处理。
这是不是意味着在#2处应该有堆栈溢出,如果有太多请求开始相互阻塞而且没有一个完成?为什么/为什么不呢?
答案 0 :(得分:4)
node.js
通过使用异步技术无处不在 1 来防止您描述的堆栈过度增长。
任何可阻止的东西都会使用回调进行进一步处理,而不是阻止调用。这样可以完全避免堆栈增长,并且可以轻松地重新进入事件循环(“驱动”底层实际I / O并请求调度)。
考虑这个伪代码:
fun() {
string = net.read();
processing(string);
}
线程在读取时被阻止,只有在读取完成后才能释放堆栈,并且processing
已完成。
现在,如果您的所有代码都是:
fun() {
net.read(onDone: processing(read_data));
}
如果你像这样实施read
:
net.read(callback) {
iorequest = { read, callback };
io.push_back(iorequest);
}
只要fun
可以使用关联的回调对读取I / O进行排队,就会完成 read
。 fun
的堆栈在没有阻塞的情况下被重绕 - 它会立即返回到事件循环而没有任何线程堆栈剩余。
即。您可以继续进行下一个回调(重新进入事件循环),而不在线程堆栈上保留任何每个请求数据。
所以node.js
通过在“用户”代码中发生阻塞调用的地方使用异步回调来避免堆栈过度增长。
有关详情,请查看node.js
'about'页面,以及最后链接的第一组幻灯片。
1 好吧,差不多我想
您在评论中提到QueueUserAPC。使用这种类型的处理,允许排队的APC进行阻塞,并且队列中的下一个APC在线程的堆栈上处理,使其成为“递归”调度。
假设我们有三个待处理的APC(A
,B
和C
)。我们得到:
初始状态:
Queue ABC
Stack xxxxxxxx
线程休眠,因此APC调度开始,进入A:
的处理Queue BC
Stack AAAAxxxxxxxx
一个块,B在同一堆栈上发送:
Queue C
Stack BBBBBBAAAAxxxxxxxx
B块,C被派遣:
Queue
Stack CCCCCCCBBBBBBAAAAxxxxxxxx
很明显,如果有足够的阻塞APC待处理,堆栈最终会爆炸。
使用node.js
,请求不允许阻止。相反,这里是对同样三个请求会发生什么的模拟:
Queue ABC
Stack xxxxxxxx
A开始处理:
Queue BC
Stack AAAAxxxxxxxx
现在A需要做一些阻止的事情 - 在node.js
中,它实际上不能。它的作用是排队另一个请求(A'
)(可能是一个上下文 - 简单地说是一个包含所有变量的哈希):
I/O queue A'
Queue BC
Stack AAAAxxxxxxxx
然后A 返回,然后又回到:
I/O queue A'
Queue BC
Stack xxxxxxxx
注意:不再是堆栈帧。 I / O挂起队列实际上由操作系统管理(使用epoll
或kqueue
或其他)。主线程检查事件循环中的OS I / O就绪状态和待处理(需要CPU)队列。
然后B得到一些CPU:
I/O queue A'
Queue C
Stack BBBBBBBxxxxxxxx
同样的事情,B想做I / O.它将新回调排队,返回。
I/O queue A'B'
Queue C
Stack xxxxxxxx
如果B的I / O请求同时完成,则下一个快照可能看起来像
I/O queue A'
Queue B'
Stack CCCCCxxxxxxxx
处理线程上没有多个回调堆栈帧。 API不提供阻止调用,该堆栈没有表现出APC模式的递归增长类型。
答案 1 :(得分:2)
node.js基于谷歌的V8 JavaScript引擎,该引擎使用了一个事件循环。
见
答案 2 :(得分:0)
使用Node.js中的事件循环有一些与使用线程不同的关键方面。
在Node.js中,运行时不会在中间中断您的函数以开始执行另一个函数。相反,您必须从返回,然后才能启动Node.js并发。
function readAndWriteItem(id) {
var callback = function(item) {
item.title = item.title + " " + item.title;
writeItem(item);
};
readItem(id, callback);
};
节点在此示例中,创建了一个回调闭包并调用了readItem
。据推测,readItem
将排队发出查询并设置自己的内部回调,以便在查询结果准备好时执行。因此,此示例函数readAndWriteItem
仅将要通过网络发送的消息排队,设置更多回调,并立即返回。一旦这个函数返回,Node.js就可以运行它的事件循环魔法。
由于函数已经返回,并且因为当你使用Node.js时这是全面的情况,所以没有堆栈溢出。