【问题标题】:NodeJS Asychronous I/O ExecutionNode JS 异步 I/O 执行
【发布时间】:2015-07-24 03:30:33
【问题描述】:

据我了解,虽然显然有一个“帮助”线程,但 Node.js 在单个线程中运行,因此,事件循环堆栈中的每个操作一个接一个地运行,其他操作并排队,而 Node 执行异步在后台进行 I/O,这样服务器就可以在执行非阻塞 I/O 的同时执行其他操作,而无需创建浪费的多个线程,I/O 完成并将其关联的回调拉入事件循环队列,这就是 Node 的重要之处。

但是,在我准备好的许多文章中,并不清楚异步 I/O 操作是否与 I/O 中的其他 I/O 操作并行运行一个单独的线程或进程,或者每个请求的 I/O 操作是否运行一个在事件循环执行其他操作时,在辅助线程中一个接一个。在阅读了“除了你的代码之外,一切都并行运行”这句话之后,这让我更加困惑。

问题是,多线程还是不多线程?如果每个异步操作都在单独的线程中运行,它使用的资源不是和 Apache 服务器一样多吗?

【问题讨论】:

  • 因此系统本身的Node进程是单线程的,而Node将I/O/Asynchronous操作委托给操作系统,操作系统又会打开新的线程来执行每个操作?这就是所谓的“线程投票”吗?
  • FS 操作通常是唯一使用线程池的请求(虽然这是在 libuv 内部处理,但不是 node.js)。其他一切都在主线程上完成。特别是网络 I/O 都是通过针对每个操作系统(例如 Linux 上的 epoll、BSD/OS X 上的 kqueue、Windows 上的 IOCP 等)轮询活动的套接字来处理的。
  • @Lhaer:thread pool 不是这样。线程池是完全不同的东西。这种技术称为非阻塞 I/O事件循环

标签: javascript node.js multithreading asynchronous


【解决方案1】:

Node 本质上是非多线程的。异步性比 Node 更深,比 libuv 更深,甚至比 libuv 使用的设施(epollkqueueIOCP 等)更深。

当内核收到异步请求时,它不会启动另一个执行线程。相反,它将它添加到一个简单的“注意事项”列表中。例如,如果一个进程发出网络读取请求,内核将在该列表上创建一个条目。这就像“嘿,下次有一个看起来像这样的读取请求时,让进程知道它。”完成此条目后,内核将控制权返回给进程,并且两者都继续进行愉快的工作。唯一幸存下来的是列表中的数据。

通过hardware interrupts. 向内核通知网络读取事件@ 使用中断,处理器将内核拉入一个特殊的循环——停止它目前正在做的任何事情——并告诉它该事件。然后内核检查它的未完成请求列表,并且(在 kevent AIO 的情况下)向进程发送一个类似的中断(以信号的形式),让它知道网络读取。所以,没有线程。只是打扰。

嗯,这有点简单:在非 AIO kevent 和 epoll 的情况下,内核在获得网络读取后,它会将其放入事件列表中。该过程会定期检查该事件列表,以查看是否有某些内容。

此外,从内核的角度来看,这就是所有 I/O 的工作方式。最大的区别是内核不需要进程等待内核返回它。

libuv 中实际上存在a little more complexity,因为非网络请求(以及 DNS 请求,这是一种特殊的、痛苦的网络请求形式)由线程处理。这是因为用于使这些异步的内核工具通常不是那么好,如果它们存在的话。

【讨论】:

  • 至少有一种语言可以在没有线程的情况下异步处理磁盘 I/O:Tcl(tcl,它早于 js,有一个内置的事件循环)。但是我知道实现它的代码很混乱,并且充满了针对操作系统特定内容的#defines:其中几乎有一半是各种风格的 Unix。然后是 Windows(Windows 重叠 io API 实际上非常好,比某些 Unixen 更好,但与其他人的非常不同)。是可以做到的,但需要付出很大的努力。
  • 很好的例子,尽管 Tcl 最初并没有附带文件事件(直到 Tk 带有事件循环之后的一段时间才出现)。在(大多数)Unix 上,它是围绕select() 调用构建的。 select() 自 1983 年以来一直存在,是许多具有事件循环的 C 语言库的基础:Xt、Qt、Gtk 等。
  • 是的,tcl的事件循环的核心是select()。对于非 Linux 的 Unixen 上的磁盘 I/O,它使用 kqueue。不确定它用于linux。可能是aio。
【解决方案2】:

它不是多线程的,只有一个小例外。让我们稍后再讨论这个异常。首先,让我们看看为什么事情可以并行而不是多线程发生。

等待 I/O

当您通过网络发送内容时,例如,发出 http 请求、等待 http 响应完成或等待 mysql 响应,您的软件没有做任何事情。

那么,如果您的软件不执行任何操作,网络如何工作? (对不起,如果这很明显,但指出明显的东西通常是让它变得明显的原因)

有些事情发生在 CPU 之外

首先,大部分网络都在您的 CPU 之外。你有你的网卡缓冲数据出入。你有网络电缆中的电子或空间/空气中的光子根据网络设备发送的信号振动。你有你的路由器、你的 ISP、地球另一端的服务器等等。

以上所有内容都需要处理,以便您的http请求返回数据。在大多数语言中,虽然上述所有情况都在发生,但您的代码不会做任何事情。

在 javascript 中并非如此。当发出 I/O 请求时,解释器不会等待数据返回,而是简单地注册一个您提供的回调,以便在数据最终到达这里时执行。现在已经完成了,其他代码可以执行。也许之前请求的其他一些数据现在在这里并且可以执行该回调。也许setTimeout 已过期,是时候调用该回调了。

因此,多件事情可以并行发生,其中大部分在您的进程之外,很多在您的 CPU 之外,其中一些在另一台机器上,甚至可能在地球的另一端。在此过程中,javascript 允许您运行一些代码。

异常:磁盘 I/O

磁盘 I/O 除外。在最低级别(实际上是次低级别),C 仅公开了用于 I/O 的同步函数,例如 fread()fwrite()。读取网络数据包在技术上也是同步的。不同之处在于网络不会立即响应,因此网络代码有大量时间等待数据包。 javascript 在这些读取和写入之间运行您的代码。但是文件系统会很高兴地告诉你数据是立即可用的。因此,与网络代码不同的是,从磁盘读取的代码大部分时间都处于忙碌状态。

对此有几种解决方法。一些操作系统甚至具有从磁盘读取的异步 API。 node.js 开发人员决定通过产生另一个线程来执行磁盘 I/O 来处理这种情况。所以对于磁盘 I/O,它是并行的,因为它是多线程的。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2013-06-19
  • 1970-01-01
  • 1970-01-01
  • 2011-03-14
  • 1970-01-01
  • 2012-04-11
  • 2012-09-14
  • 1970-01-01
相关资源
最近更新 更多