Node.js何时阻止?

时间:2016-04-16 12:22:39

标签: node.js multithreading asynchronous event-handling

我已经使用了Node.js一段时间了,我才意识到它可以阻塞。我无法将我的大脑包裹在Node.js阻塞的条件下。

  • 所以,Node.js是单线程的,因为(i)Javascript是和(ii) 避免所有多线程陷阱。
  • 尽管是单线程的,但要同时做很多事情 实现异步执行。所以,与DB(I / O)交谈 通常)是非阻塞的(因为它是异步的)。
  • 但是,所有传入的请求都要做一些工作(即与DB交谈)和 必须回到客户端的所有工作结果(即发送 一些数据)他们使用那个单线程。
  • Node.js使用"事件循环"在那个单线程里面得到所有 请求并将它们分配给非阻塞I / O任务。

因此I / O任务由于异步回调而是非阻塞的,但是单个线程可能是阻塞的,因为它是同步的,因为事件循环可能被阻塞,因为很多复杂的请求出现在同一时间?

  1. 我是对的,我理解正确吗?我猜,我不是因为 herehere他们强调" Node是单线程的 这意味着你的代码没有并行运行"。实际上是什么 是什么意思以及它如何阻止节点?
  2. 因此,事件循环永远运行并始终搜索请求,或 它在发现新请求后开始执行?
  3. 节点阻塞弱点是否会使节点无用 项目,并使其最终只适用于微型网站和 小项目?
  4. 非常感谢。

3 个答案:

答案 0 :(得分:8)

首先,要明确:node.js作为一个整体不是单线程。 Node通过libuv确实有一个线程池,它用于执行某些任务,这些任务目前无法从大多数平台上的单个线程(例如文件I / O)有效地执行,或者它们本身就是计算密集型的(例如zlib)。应该注意的是,大多数crypto模块(本身也是计算密集型)当前没有异步/非阻塞接口(crypto.randomBytes()除外)。

v8还利用多个线程来执行垃圾收集,函数优化等操作。

然而,节点中的其他所有内容确实发生在同一个单线程中。

现在专门解答您的问题:

  1. javascript代码是从单个线程运行的事实并不会使节点阻塞。正如this answer所解释的那样,节点最重要的是(I / O)并发而不是(代码)并行。例如,在多核/ cpu系统上,你可以通过内置的cluster模块并行运行 并行运行节点代码,但节点的主要目标是能够同时处理大量I / O而不为每个套接字/服务器/等提供一个线程。

  2. 有一个很好的,详细的写作here,它描述了节点中的事件循环是如何工作的。

  3. 如前所述,Node的主要目标是很好地处理I / O,这适用于Web应用程序和任何类型的网络程序的大多数用例。

    如果您的脚本受CPU限制(例如,您正在计算pi或转码音频/视频),那么最好将该工作委托给节点中的子进程(例如,呼叫ffmpeg用于代码转换而不是在javascript中执行或在节点的主线程中的c ++节点插件中同步执行。如果您没有同时执行任何其他操作(例如处理HTTP请求),那么可以执行这些阻止事务。有许多人会以这种方式使用节点来执行各种实用程序任务,其中I / O并发性并不重要。其中一个例子可能是执行js和css文件的缩小,linting和/或捆绑的脚本,或者是从大量图像创建缩略图的脚本。

    但是,如果您的脚本改为创建TCP或HTTP服务器,例如从数据库中提取信息,对其进行格式化并将其发送回用户,那么节点将擅长这样做,因为大部分时间都花在了在这个过程中,只是等待套接字/ HTTP客户端发送(更多)数据并等待数据库回复查询结果。

答案 1 :(得分:3)

让我们直接回答。

  1. 是的,Node.js是完全阻止的,如果你这样看。让我们假设您从数据库中读取了一个巨大的,半千兆字节的CSV文件,并尝试对其进行json编码并发送给客户端(天真,我知道,但请耐心等待。)
  2. JSON编码基本上是字符串操作。这在很多语言中都很慢,而不仅仅是JavaScript。因此,如果编码这样的json花费20秒,您将加载此CSV文件(异步),但是您将花费20秒来解析字符串。在此期间,没有其他任何东西可以进入 - 没有其他回调,而不是其他可以同时发送到数据库/文件系统的请求 - 除了单个" JSON.stringify()之外,没有任何实际的编程运行"功能

    有很多方法可以解决这个问题,但你应该知道它 - 如果你的单个函数或像JSON.stringify这样的单个语句需要很多,它就会阻塞。您需要考虑到这一点来编写应用程序。

    1. 实质上,事件循环正在休眠,直到其主队列中没有任务。可以有其他队列,例如回调队列,您可以等待数十个数据库操作的结果。但是他们不会在你的主事件循环中,直到他们实际上被数据库回复回叫。
    2. 说你正在从上面的1)解析那个JSON。在此期间,您会收到5个新请求或其他内容。您的5个请求直接进入队列,并且当一个请求完成时,事件循环检查下一个要处理的请求。如果没有,它会等待。

      1. 阻止不会使节点无效。如果是这样的话,那些 async的其他单线程语言会像大规模那样做什么?
      2. Node已经在大型项目中使用了,我相信如果你谷歌一点,你可以找到很多。诀窍是使用适当的工具来获得正确的解决方案 - 因此,Node.js可能需要不同的策略来处理CPU密集型任务,或者甚至可能不是正确的工具。

答案 2 :(得分:1)

让我检查一下是否有这个。

Node.js是单线程的,因此它的代码不能并行运行,但它的I / O可以是并发的。我们使用异步javascript函数。这就是为什么I / O是非阻塞的原因。

  

Node.js为您的代码保留一个线程......但是,除了您的代码之外,所有内容都是并行运行。

     

做一个"睡眠"例如,将阻止服务器一秒钟。 - 单线程代码

     

所有I / O都是异步和异步的,因此以下内容不会阻止服务器:c.query( 'SELECT SLEEP(20);', .... - " sleep"是一个异步函数,查询 - 非阻塞I / O.   (来自here

为了管理传入的请求,Node实现了"事件循环"。

  

事件循环是"一个处理和处理外部事件并将它们转换为回调调用的实体"。所以I / O调用是Node.js可以从一个请求切换到另一个请求的点。在I / O调用中,您的代码保存回调并将控制权返回给node.js运行时环境。当数据实际可用时,稍后将调用回调。   (来自here

因此I / O是非阻塞的,因为Node可以执行其他操作而不是等待某些I / O完成。

如果请求可能需要很长时间才能回答,Node会从该线程池中为该请求分配一个线程。

  

该线程负责接收该请求,处理该请求,执行阻塞IO操作,准备响应并将其发送回事件循环   事件循环依次将响应发送到相应的客户端。 (来自here

  1. 因此,如果有很多简单的请求,I / O是非阻塞的,并且这些请求的所有回调都非常快。
  2. (从现在开始,我不确定我是否正确使用了它)

    1. 很多简单的请求和复杂的请求。复杂的任务可能是繁重的任务,图像调整大小算法或任何需要时间的事情。每个请求都在异步函数中,节点将为复杂的函数定义一个线程。大多数简单的将立即作出回应。复杂的一个将花费一些时间,在自己的线程内,而简单的仍然响应(导致非阻塞)。但是事件循环中的回调按特定顺序排队(FIFO,对吧?)。
        

      在循环中,轮询队列以查找下一条消息(每个轮询称为“tick”),当遇到消息时,将执行该消息的回调。调用此回调函数充当调用堆栈中的初始帧,并且由于JavaScript是单线程的,因此在堆栈上的所有调用返回之前,将停止进一步的消息轮询和处理。后续(同步)函数调用将新的调用帧添加到堆栈(来自here

    2. 因此,在复杂的回调之后的简单请求的回调将需要一些时间来响应,因为复杂的回调将花费大量时间。

      1. 许多复杂的请求,每个请求都在自己的异步函数中。如果每个请求需要1秒才能响应,并且我们有10000个响应,则时间总和。它们最终总结在使用事件循环的单线程节点中。在事件循环中,每个需要花费大量时间来响应的回调都会排在另一个需要大量时间响应的回调之后。

      2. 我认为上面描述了格兰特的问题here。这是关于节点缺点的第一篇文章,我还不知道我是否正确使用了它。所以,

          

        我们的Node服务可能已经处理过传入请求,如果所有需要做的就是返回立即可用的数据。

      3. 但是

          

        Node是单线程的,这意味着您的代码都不会并行运行。 I / O可能不会阻止服务器,但您的代码肯定会阻止。如果我呼叫睡眠5秒钟,我的服务器将在此期间无响应。

        Grant发现自己有很多请求需要花时间,因为亚马逊服务很慢

          

        ......正在等待大量的嵌套回调,这些回调都依赖于来自S3的反应(有时可能很糟糕)

        然后事件循环杀死了所有东西

          

        在循环中,轮询队列以查找下一条消息(每个轮询称为“tick”),当遇到消息时,将执行该消息的回调。调用此回调函数充当调用堆栈中的初始帧,并且由于JavaScript是单线程的,因此在堆栈上的所有调用返回之前,将停止进一步的消息轮询和处理。后续(同步)函数调用将新的调用帧添加到堆栈中.....当发生任何请求超时时,事件及其关联的回调被置于已经过载的消息队列中。虽然超时事件可能发生在1秒,但是在队列中当前存在的所有其他消息及其相应的回调代码完成执行(可能在几秒钟之后)之后,回调才会被处理。

        我不知道我是否正确得到了这个。请随意指出我的错误并帮助我完成所有事情。

        由于