单线程非阻塞IO模型如何在Node.js中工作

我不是Node程序员,但我对单线程非阻塞IO模型的工作原理感兴趣。 在我阅读了文章Understanding-the-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();
    }
);

这里有一个问题。 当有两个请求A(第一个)和B时,由于只有一个线程,服务器端程序将首先处理请求A:执行sql查询,这是一个表示I / O等待的睡眠语句。 并且该程序在I / O等待中被阻塞,并且不能执行呈现该网页的代码。 程序在等待期间会切换到请求B吗? 在我看来,由于单线程模型,没有办法从另一个请求切换一个请求。 但示例代码的标题说“除了您的代码外,所有内容都可以并行运行”。 (PS我不确定是否误解了代码,因为我从来没有使用节点。)在等待期间,节点如何将A切换到B? 你能以简单的方式解释Node的单线程非阻塞IO模型吗? 如果你能帮助我,我将不胜感激。 :)


Node.js基于libuv,它是一个跨平台的库,它抽象支持受支持的操作系统(至少Unix,OS X和Windows)提供的异步(非阻塞)输入/输出的apis / syscalls。

异步IO

在这个编程模型中,对文件系统管理的设备和资源(套接字,文件系统等)的打开/读/写操作不会阻塞调用线程(如典型的同步类似c模型),只需标记进程(在内核/ OS级别的数据结构中)在新数据或事件可用时通知。 如果是类似网络服务器的应用程序,则该过程负责确定通知的事件属于哪个请求/上下文,并继续处理来自此处的请求。 请注意,这必然意味着您将与发起请求的操作系统处于不同的堆栈框架,因为后者不得不屈服于进程调度程序,以便单个线程进程处理新事件。

我所描述的模型的问题在于,对于程序员来说,它并不熟悉和难以推断,因为它本质上是非顺序的。 “您需要在功能A中提出请求,并将结果处理为不同的功能,A中的当地人通常不可用。”

节点的模型(Continuation Passing Style and Event Loop)

Node利用JavaScript的语言特性解决了这个问题,通过诱导程序员采用某种编程风格,使这个模型更加同步。 每个请求IO的函数都有一个像function (... parameters ..., callback)的签名function (... parameters ..., callback)并且需要给出一个回调function (... parameters ..., callback)当请求的操作完成时会调用它(请记住,大部分时间都是等待的为操作系统发出完成信号 - 可以用来完成其他工作的时间)。 Javascript对闭包的支持允许你使用你在回调体内的外部(调用)函数中定义的变量 - 这允许在不同的函数之间保持状态,这些函数将由节点运行时独立调用。 另请参见延续传球风格。

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

您可以将节点的事件循环看作与内核的调度程序有点相似 :内核会在其未完成的IO完成后计划执行阻塞的线程,而节点将在发生相应事件时安排回调。

高并发性,无并行性

作为最后一句话,“除了你的代码外,所有的东西都是并行运行的”这句话的确体现了节点允许你的代码同时处理来自成千上万个开放套接字的请求,同时通过多路复用和排序你所有的js单一执行流中的逻辑(尽管说“一切都并行运行”在这里可能是不正确的 - 请参阅并发与并行 - 有什么区别?)。 这对于Web应用程序服务器非常有效,因为大部分时间实际上用于等待网络或磁盘(数据库/套接字),而逻辑并不是真正的CPU密集型 - 也就是说: 这适用于IO绑定的工作负载


那么,为了给出一些观点,让我将node.js与apache进行比较。

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

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

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

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

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

实际上,节点服务器一直为你完成。 为了使开关,(异步行为),你会使用的大多数函数将有回调。

编辑

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

  • 打开连接到数据库,连接本身可以异步。
  • 一旦db连接,查询就被传递到服务器。 查询可以排队。
  • 主事件循环通过回调或事件得到完成通知。
  • 主循环执行您的回调/事件处理程序。
  • 对http服务器的传入请求以类似的方式处理。 内部线程结构如下所示:

    node.js事件循环

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


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

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

    链接地址: http://www.djcxy.com/p/13913.html

    上一篇: How the single threaded non blocking IO model works in Node.js

    下一篇: What happens when a computer program runs?