6

node 的 javascript 环境是单线程的,还是所有事情都同时发生?或者(更有可能)这些陈述都没有解释节点发生了什么。

我是节点新手,并试图了解它如何处理回调。我在这个主题上的谷歌搜索没有被证明是富有成效的,而且似乎有多个受众使用诸如“线程、锁定和单线程”之类的术语,每个受众都有不同的上下文,而且我没有足够的节点经验来正确解析我在读什么。

根据我的阅读,node 的 javascript 执行环境就像浏览器一样,是单线程的。也就是说,尽管一切都是围绕异步回调设计的,但一切都以确定的顺序发生,并且永远不会有两个线程同时修改同一个变量或运行语句。我也读过这意味着节点程序员用户不必担心锁定语义。

如果我在浏览器领域并使用流行的 javascript 库之一来设置非 dom-event-handler 回调,例如

console.log("Here");
$.each([1,2,3],function(){
    console.log("-- inside the callback --");
});
console.log("There");

我的输出始终如一

Here
-- inside the callback --
-- inside the callback --
-- inside the callback --
There

但是,如果我在节点 js 中使用回调执行类似操作(从命令行将其作为 shell 脚本运行)

var function example()
{
    var fs = require('fs'); 
    console.log("Here");
    fs.readdir('/path/to/folder', function(err_read, files){
        console.log('-- inside the callback --');            
    });
    console.log("There");
    for(var i=0;i<10000;i++)
    {
        console.log('.');
    }
}
example();
console.log("Reached Top");  

我一直(似乎 - 见上面的“没有太多经验”)得到这样的结果

Here
There
.
. (repeat 10,000 times)
Reached Top
-- inside the callback --    

也就是说,节点example在调用回调之前完成了函数的执行。

这是节点中的确定性行为吗?或者有时会在example函数完成之前调用回调?还是将取决于使用回调的库中的实现?

我理解 node 背后的想法是编写基于事件的代码——但我试图了解 node 真正在做什么,以及哪些行为可以依赖,哪些行为不能。

4

3 回答 3

2

node 的 javascript 环境是单线程的,还是所有事情都同时发生?

Node 具有单线程程序执行模型,这意味着在一个节点进程内任何时候都只会执行一条指令。执行将继续,直到程序产生控制权。这可能发生在程序代码的末尾,或者当回调到达它的末尾时。

在第一种情况下:

console.log("Here");
$.each([1,2,3],function(){
    console.log("-- inside the callback --"); 
});
console.log("There");

它们的关键是同步$.each使用回调的事实,因此它有效地以确定的顺序调用它的回调。

现在在第二种情况下,异步fs.readdir使用回调- 它使回调等待事件被触发(即,当读取目录完成时)。这可能随时发生。但是,调用函数不会产生控件,因此总是会在调用任何回调之前完成。example

一句话:函数/全局范围的所有代码都在该函数/全局范围中定义的任何回调之前执行。

于 2013-08-08T22:33:05.063 回答
1

我想在@dc5 的回答中补充一点。在它的网站上,他们将节点描述为

Node.js 使用事件驱动的非阻塞 I/O 模型

事件驱动部分非常重要。当我无法理解节点程序的执行模型时,它通常会消除我的疑虑。

因此,以您为例,正在发生的事情是:

  1. 这里打印到控制台。

  2. 对文件进行异步调用。

  3. Node 将监听器附加到异步调用。

  4. 继续执行行并打印出来。

现在可能是异步调用在当前正在执行的代码之前完成,但节点 js 完成了手头的任务,然后返回为异步调用提供服务。

因此,它不再等待任何事情,而是继续执行。这就是为什么 node js 执行循环永远不会空闲的原因。

于 2013-08-09T08:50:22.127 回答
1

在这种情况下,您正在调用一个正在执行 IO 并且是异步的函数。这就是为什么您会以现在的方式看到输出。

因为您的其余代码是内联执行的 - 在单个执行线程上,它在 IO 事件有机会进入事件循环之前完成。

我会说期望这种行为,但不要绝对确定地依赖它,因为它确实依赖于使用回调的库的实现。一个(坏的、邪恶的)实现者可能会发出一个同步请求以查看是否有任何工作要做,如果没有,则立即回调。(希望你永远不会看到这个,但是……)。

于 2013-08-08T22:36:09.840 回答