我不是Node程序员,但我对单线程非阻塞IO模型如何工作感兴趣。 在我阅读了理解node-js-event-loop这篇文章之后,我真的很困惑。 文中给出了模型的一个例子:

c.query(
   'SELECT SLEEP(20);',
   function (err, results, fields) {
     if (err) {
       throw err;
     }
     res.writeHead(200, {'Content-Type': 'text/html'});
     res.end('<html><head><title>Hello</title></head><body><h1>Return from async DB query</h1></body></html>');
     c.end();
    }
);

Que: When there are two requests A(comes first) and B since there is only a single thread, the server-side program will handle the request A firstly: doing SQL querying is asleep statement standing for I/O wait. And The program is stuck at the I/O waiting, and cannot execute the code which renders the web page behind. Will the program switch to request B during the waiting? In my opinion, because of the single thread model, there is no way to switch one request from another. But the title of the example code says that everything runs in parallel except your code.

(注:我不确定我是否误解了代码,因为我有 从未使用过Node。)在等待过程中,节点如何将A切换到B ?并且可以 你解释了Node的单线程非阻塞IO模型 简单的方法吗?如果你能帮助我,我会很感激的。:)


当前回答

对于那些寻求简单答案而不想深入Node.js内部的人。

Node.js不是单线程的,它默认运行在5个线程上。

是的,只有一个线程用于实际的JavaScript处理,但它总是在函数之间切换。

它将SQL查询发送到数据库,并让它在其他线程中等待,而单线程Node.js继续计算一些准备计算的其他代码。

如果你想要更多的解释,这里有关于事件循环、工作池和整个libuv文档的好文章。

其他回答

Node.js建立在libuv的基础上,libuv是一个跨平台的库,它抽象了由受支持的操作系统(至少Unix、OS X和Windows)提供的异步(非阻塞)输入/输出的api /系统调用。

异步IO

In this programming model open/read/write operation on devices and resources (sockets, filesystem, etc.) managed by the file-system don't block the calling thread (as in the typical synchronous c-like model) and just mark the process (in kernel/OS level data structure) to be notified when new data or events are available. In case of a web-server-like app, the process is then responsible to figure out which request/context the notified event belongs to and proceed processing the request from there. Note that this will necessarily mean you'll be on a different stack frame from the one that originated the request to the OS as the latter had to yield to a process' dispatcher in order for a single threaded process to handle new events.

我所描述的模型的问题在于,它对程序员来说并不熟悉,而且很难推理,因为它本质上是非顺序的。“你需要在函数A中提出请求,并在不同的函数中处理结果,而来自A的局部变量通常不可用。”

节点模型(延续传递样式和事件循环)

Node tackles the problem leveraging javascript's language features to make this model a little more synchronous-looking by inducing the programmer to employ a certain programming style. Every function that requests IO has a signature like function (... parameters ..., callback) and needs to be given a callback that will be invoked when the requested operation is completed (keep in mind that most of the time is spent waiting for the OS to signal the completion - time that can be spent doing other work). Javascript's support for closures allows you to use variables you've defined in the outer (calling) function inside the body of the callback - this allows to keep state between different functions that will be invoked by the node runtime independently. See also Continuation Passing Style.

此外,在调用产生IO操作的函数后,调用函数通常会将控制权返回给节点的事件循环。此循环将调用计划执行的下一个回调或函数(很可能是因为操作系统通知了相应的事件)—这允许并发处理多个请求。

你可以把节点的事件循环想象成有点类似于内核的调度程序:内核会在其挂起的IO完成后调度执行阻塞的线程,而节点会在相应的事件发生时调度回调。

高度并发,无并行性

As a final remark, the phrase "everything runs in parallel except your code" does a decent job of capturing the point that node allows your code to handle requests from hundreds of thousands open socket with a single thread concurrently by multiplexing and sequencing all your js logic in a single stream of execution (even though saying "everything runs in parallel" is probably not correct here - see Concurrency vs Parallelism - What is the difference?). This works pretty well for webapp servers as most of the time is actually spent on waiting for network or disk (database / sockets) and the logic is not really CPU intensive - that is to say: this works well for IO-bound workloads.

好吧,为了给出一些观点,让我比较node.js和apache。

Apache是一个多线程的HTTP服务器,对于服务器接收到的每个请求,它都会创建一个单独的线程来处理该请求。

Node.js是事件驱动的,从单个线程异步处理所有请求。

当在apache上接收到A和B时,会创建两个线程来处理请求。每个服务器分别处理查询,每个服务器在提供页面之前等待查询结果。该页面只在查询完成之前提供。查询获取阻塞是因为服务器在收到结果之前无法执行线程的其余部分。

在node中,c.query是异步处理的,这意味着当c.query获取A的结果时,它会跳转到处理B的c.query,当A的结果到达时,它会将结果发送回回回调,后者会发送响应。Node.js知道在取回完成时执行回调。

在我看来,因为它是一个单线程模型,所以没有办法 从一个请求切换到另一个请求。

实际上,节点服务器一直都在为你做这件事。要进行切换,(异步行为)您将使用的大多数函数将具有回调。

Edit

SQL查询来自mysql库。它实现回调样式以及事件发射器来排队SQL请求。它不异步执行它们,这是由提供非阻塞I/O抽象的内部libuv线程完成的。查询的步骤如下:

打开一个到db的连接,连接本身可以异步进行。 一旦连接db,查询就被传递到服务器。查询可以排队。 主事件循环通过回调或事件得到完成通知。 主循环执行回调/事件处理程序。

以类似的方式处理对http服务器的传入请求。内部线程架构是这样的:

c++线程是做异步I/O(磁盘或网络)的libuv线程。主事件循环在将请求分派到线程池后继续执行。它可以接受更多的请求,因为它不会等待或休眠。SQL查询/HTTP请求/文件系统读取都是以这种方式发生的。

Node.js在幕后使用libuv。Libuv有一个线程池(默认大小为4)。因此Node.js确实使用线程来实现并发性。

然而,你的代码在一个线程上运行(也就是说,Node.js函数的所有回调都将在同一个线程上调用,所谓的循环线程或事件循环)。当人们说“Node.js运行在单线程上”时,他们实际上是在说“Node.js的回调在单线程上运行”。

如果你读得更远一点——“当然,在后端,有用于DB访问和进程执行的线程和进程。然而,这些并没有显式地暴露在你的代码中,所以你不必担心它们,除非知道从每个请求的角度来看,例如与数据库或与其他进程的I/O交互将是异步的,因为这些线程的结果通过事件循环返回到你的代码中。”

about - "除了你的代码,所有东西都是并行运行的" -你的代码是同步执行的,每当你调用一个异步操作,比如等待IO,事件循环处理所有事情并调用回调。这不是你需要考虑的事情。

在你的例子中:有两个请求A(先出现)和B。你执行请求A,你的代码继续同步运行并执行请求B。事件循环处理请求A,当它完成时,它调用请求A的回调结果,同样的情况发生在请求B。

函数c.query()有两个参数

c.query("Fetch Data", "Post-Processing of Data")

在这种情况下,“获取数据”操作是一个DB-Query,现在这可能由Node.js通过衍生出一个工作线程来处理,并赋予它执行DB-Query的任务。(记住Node.js可以在内部创建线程)。这使得函数能够立即返回,没有任何延迟

第二个参数“Post-Processing of Data”是一个回调函数,节点框架注册这个回调函数,并由事件循环调用。

因此,语句c.query (paramenter1, parameter2)将立即返回,使node能够满足另一个请求。

附注:我刚刚开始理解节点,实际上我想把这作为对@Philip的评论,但由于没有足够的声誉点,所以把它作为答案写了下来。