我不是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模型
简单的方法吗?如果你能帮助我,我会很感激的。:)
好的,到目前为止,大部分事情应该都清楚了……棘手的部分是SQL:如果它实际上不是在另一个线程或进程中运行,SQL的执行必须被分解成单独的步骤(由一个为异步执行而设计的SQL处理器!),其中执行非阻塞的步骤,而阻塞的步骤(例如睡眠)实际上可以传输到内核(作为警报中断/事件),并放在主循环的事件列表中。
这意味着,例如,SQL的解释,等等是立即完成的,但在等待期间(存储为一个事件,将来由内核在一些kqueue, epoll,…结构;与其他IO操作一起),主循环可以做其他事情,并最终检查这些IOs是否发生了什么并等待。
所以,换句话说:程序永远不会(被允许)卡住,休眠调用永远不会被执行。它们的职责是由内核(写一些东西,等待一些东西通过网络,等待时间流逝)或另一个线程或进程完成的。- Node进程检查在每个事件循环周期中,内核是否在唯一的阻塞调用中完成了至少一项任务。当所有非阻塞的事情都完成时,就达到了这一点。
清楚了吗?: -)
我不知道Node。但是c.query是从哪里来的呢?
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.
函数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的评论,但由于没有足够的声誉点,所以把它作为答案写了下来。
事件循环允许Node.js执行非阻塞I/O操作——尽管JavaScript是单线程的——只要有可能就将操作卸载给系统内核。可以将事件循环看作管理器。
新请求被发送到队列中,并由同步事件解复用器监视。如您所见,每个操作处理程序也已注册。
Then those requests are sent to the thread pool (Worker Pool) synchronously to be executed. JavaScript cannot perform asynchronous I/O operations. In browser environment, browser handles the async operations. In node environment, async operations are handled by the libuv by using C++. Thread's pool default size is 4, but it can be changed at startup time by setting the UV_THREADPOOL_SIZE environment variable to any value (maximum is 128). thread pool size 4 means 4 requests can get executed at a time, if event demultiplexer has 5 requsts, 4 would be passed to thread pool and 5th would be waiting. Once each request gets executed, result is returned to the `event demultiplexer.
当一组I/O操作完成时,事件解复用器将一组对应的事件推入事件队列。
Handler是回调。现在,事件循环监视事件队列,如果有准备好的事件,它将被推入堆栈以执行回调。记住,回调最终是在堆栈上执行的。请注意,一些回调对其他回调具有优先级,事件循环确实根据它们的优先级选择回调。