首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >单线程非阻塞IO模型在Node.js中的工作方式

单线程非阻塞IO模型在Node.js中的工作方式
EN

Stack Overflow用户
提问于 2013-02-10 13:54:17
回答 8查看 111.8K关注 0票数 341

我不是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();
    }
);

SQL:当有两个请求A(最先出现)和B时,因为只有一个线程,所以服务器端程序将首先处理请求A:执行查询是休眠语句,代表I/O等待。并且程序被阻塞在I/O等待处,并且不能执行呈现后面的网页的代码。在等待期间,程序会切换到请求B吗?在我看来,由于单线程模型,无法从一个请求切换到另一个请求。但是示例代码的标题显示,除了代码之外,所有的代码都是并行运行的。

(P.S我不确定我是否误解了代码,因为我从来没有使用过Node.) Node在等待过程中如何将A切换到B?你能简单地解释一下Node的单线程非阻塞IO模型吗?如果你能帮助我,我将不胜感激。:)

EN

回答 8

Stack Overflow用户

回答已采纳

发布于 2013-02-10 19:41:37

Node.js构建在libuv之上,这是一个跨平台库,为受支持的OSes (至少是Unix、OS和Windows )提供的异步(非阻塞)输入/输出抽象apis/系统调用。

异步IO

在此编程模型中,设备和资源(套接字、文件系统等)上的打开/读/写操作由文件系统管理,不要阻塞调用线程(就像在典型的同步类c模型中一样),只需标记进程(在内核/OS级数据结构中),以便在有新数据或事件可用时得到通知。如果是类似web服务器的应用程序,则流程负责找出通知的事件属于哪个请求/上下文,并从那里继续处理请求。请注意,这必然意味着您将处于与向OS发出请求的堆栈框架不同的堆栈框架上,因为后者必须屈从于进程的调度程序,以便让单线程进程处理新事件。

我所描述的模型的问题是,对于程序员来说,它并不熟悉,也很难推理,因为它本质上是非顺序的。“您需要在函数A中发出请求,并在另一个函数中处理结果,在该函数中,来自A的本地变量通常不可用。”

节点的模型(延续传递风格和事件循环)

Node利用javascript的语言特性解决了这个问题,通过诱导程序员使用某种编程风格,使这个模型看起来更同步。每个请求IO的函数都有一个类似于function (... parameters ..., callback)的签名,并且需要提供一个回调函数,该回调函数将在请求的操作完成时调用(请记住,大部分时间都花在等待操作系统发出完成信号-可以用来做其他工作的时间)。Javascript对闭包的支持允许您使用在回调主体内的外部(调用)函数中定义的变量-这允许在将由节点运行时独立调用的不同函数之间保持状态。另请参见Continuation Passing Style

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

您可以将节点的事件循环看作类似于内核的分派器的:内核将调度阻塞的线程在其挂起的IO完成后执行,而节点将在相应的事件发生时调度回调。

高并发,无并行

最后,“除了你的代码之外的所有东西都是并行运行的”这句话很好地抓住了这一点,节点允许你的代码通过在一个单一的执行流中对所有的js逻辑进行多路复用和排序,用一个线程并发地处理来自数十万个打开的套接字的请求(即使在这里说“所有东西都并行运行”可能是不正确的-请参阅Concurrency vs Parallelism - What is the difference?)。这对于webapp服务器非常有效,因为大多数时间实际上都花在等待网络或磁盘(数据库/套接字)上,并且逻辑并不是真正的CPU密集型-也就是说:这对于IO绑定的工作负载很有效。

票数 391
EN

Stack Overflow用户

发布于 2013-02-10 19:09:52

好吧,为了提供一些观点,让我来比较一下node.js和apache。

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

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

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

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

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

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

编辑

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

  1. 打开到数据库的连接,连接本身即可连接到数据库,查询将传递到服务器。查询可以排队。
  2. 通过回调或事件通知主事件循环完成。
  3. 主循环执行您的查询

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

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

票数 219
EN

Stack Overflow用户

发布于 2016-11-29 03:56:17

Node.js在幕后使用libuv。libuv has a thread pool (默认为4)。因此,Node.js 确实使用线程来实现并发。

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

票数 58
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/14795145

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档