为啥 node.js 是异步的?
Posted
技术标签:
【中文标题】为啥 node.js 是异步的?【英文标题】:Why is node.js asynchronous?为什么 node.js 是异步的? 【发布时间】:2013-07-10 13:05:36 【问题描述】:实际上没有人问过这个问题(从我得到的所有“建议”以及在我在这里问之前的搜索中)。
那么为什么 node.js 是异步的呢?
根据我的一些研究得出的结论:
像 php 和 Python 这样的语言是脚本语言(我可能对脚本语言的实际语言有误),而 javascript 不是。 (我想这是因为 JS 无法编译?)
Node.js 在单个线程上运行,而脚本语言使用多个线程。
异步意味着无状态,并且连接是持久的,而同步则(几乎)相反。
也许在上面所说的某个地方找到了答案,但我仍然不确定。
我与此主题相关的第二个也是最后一个问题是:
可以把 JavaScript 做成同步语言吗?
PS。我知道你们中的一些人会问“为什么要让 JS 同步?”在你的回答中,但事实是我没有。我只是问这些类型的问题,因为我敢肯定有更多的人,而不仅仅是我自己考虑过这些问题。
【问题讨论】:
谢谢你们。您所有的答案都很棒,并为上述问题增加了不同的价值。我不会将任何一个答案选为最佳答案,因为我相信您的所有答案都会对其他阅读此问题的人很重要。 【参考方案1】:Node.js 在单个线程上运行,而脚本语言使用多个线程。
技术上不是。 Node.js 使用多个线程,但只有一个执行线程。后台线程用于处理 IO 以使所有异步功能正常工作。高效地处理线程是一件非常痛苦的事情,因此下一个最佳选择是在事件循环中运行,这样代码就可以在后台线程被 IO 阻塞时运行。
异步意味着无状态,并且连接是持久的,而同步则(几乎)相反。
不一定。您可以很容易地在异步系统中保存状态。例如,在 Javascript 中,您可以使用 bind()
将 this
绑定到函数,从而在函数返回时显式保留状态:
function State()
// make sure that whenever doStuff is called it maintains its state
this.doStuff = this.doStuff.bind(this);
State.prototype.doStuff = function ()
;
异步意味着不等待操作完成,而是注册一个监听器。这种情况在其他语言中一直发生,尤其是任何需要接受用户输入的语言。例如,在 Java GUI 中,您不会阻止等待用户按下按钮,而是向 GUI 注册一个侦听器。
我与此主题相关的第二个也是最后一个问题是:
可以把 JavaScript 做成同步语言吗?
从技术上讲,所有语言都是同步的,即使是 Javascript。但是,Javascript 在异步设计中工作得更好,因为它被设计为单线程。
基本上有两种类型的程序:
CPU 受限 - 使其运行得更快的唯一方法是获得更多 CPU 时间 IO 受限 - 花费大量时间等待数据,因此更快的处理器无关紧要视频游戏、数字运算器和编译器受 CPU 限制,而 Web 服务器和 GUI 通常受 IO 限制。 Javascript 相对较慢(因为它非常复杂),因此它无法在 CPU 密集型场景中竞争(相信我,我已经编写了相当一部分 CPU 密集型 Javascript)。
Javascript 不是根据类和对象进行编码,而是根据可以串在一起的简单函数进行编码。这在异步设计中非常有效,因为可以编写算法以在数据进入时增量处理数据。IO(尤其是网络 IO)非常慢,因此数据包之间存在相当长的时间。
示例
假设您有 1000 个实时连接,每个连接每毫秒发送一个数据包,处理每个数据包需要 1 微秒(非常合理)。我们还假设每个连接发送 5 个数据包。
在单线程同步应用程序中,每个连接都将被串行处理。总时间为 (5*1 + 5*.001) * 1000 毫秒,或 ~5005 毫秒。
在单线程异步应用程序中,每个连接都将被并行处理。由于每个数据包需要 1 毫秒,而处理每个数据包需要 0.001 毫秒,因此我们可以在数据包之间处理每个连接的数据包,因此我们的公式变为:1000*.001 + 5*1 毫秒,即 ~6 毫秒。
解决这个问题的传统方法是创建更多线程。这解决了 IO 问题,但是当连接数增加时,内存使用量(线程消耗大量内存)和 CPU 使用量(将 100 个线程多路复用到 1 个核心比 1 个线程在 1 个核心上更难)也增加了。
但是,也有缺点。如果您的 Web 应用程序碰巧也需要进行一些繁重的数字运算,那么您就是 SOL,因为当您在运算数字时,连接需要等待。线程解决了这个问题,因为当数据准备好等待 IO 的线程时,操作系统可以交换 CPU 密集型任务。此外,node.js 绑定到单核,因此您无法利用多核处理器,除非您启动多个实例和代理请求。
【讨论】:
【参考方案2】:Javascript 不会编译成任何东西。它在运行时被“评估”,就像 PHP 和 Ruby 一样。因此,它是一种类似于 PHP/Ruby 的脚本语言。 (它的正式名称实际上是 ECMAScript)。
Node 遵循的“模型”与 PHP/Ruby 有点不同。 Node.js 使用一个“事件循环”(单线程),其一个目标是接受网络请求并非常快速地处理它们,如果由于任何原因它遇到需要一段时间的操作(API 请求,数据库查询 -基本上任何涉及 IO(输入/输出)的东西,它都会将其传递给后台“工作”线程,然后在工作线程等待长任务完成时去做其他事情。发生这种情况时,主“事件循环”将获取结果并继续处理它们。
PHP/Ruby 遵循线程模型。本质上,对于每个传入的网络请求,应用程序服务器都会启动一个孤立的线程或进程来处理请求。这并不能很好地扩展,与此模型相比,Node 的方法被认为是其核心优势之一。
异步意味着无状态并且连接是持久的 而同步是(几乎)相反的。
没有。同步指令按自然顺序完成,从头到尾。异步指令是指如果程序流程中的某个步骤耗时较长,程序会继续执行操作,完成后直接返回该操作。
可以把 JavaScript 做成同步语言吗?
JavaScript 中的某些操作是同步的。其他是异步的。 例如:
阻塞操作:
for(var k = 0; k < 1; k = k - 1;)
alert('this will quickly get annoying and the loop will block execution')
alert('this is blocked and will never happen because the above loop is infinite');
异步:
jQuery.get('/foo', function (result) alert('This will occur 2nd, asynchronously'); );
alert('This will occur 1st. The above operation was skipped over and execution continued until the above operation completes.');
【讨论】:
Javascript 不能编译成任何东西。 是错误的。 Node.js 使用 V8,其中包含一个 JIT 编译器,可将 Javascript 编译为机器代码。对于 PHP,有 plenty solutions that can compile the code。 Ruby 不评估源代码,而是在抽象语法树上运行。 -> 语言本身并不暗示它是被解释的还是编译的,但问题所涉及的具体实现不是经典意义上的解释。 @Fresheyeball “V8 将 JavaScript 源代码在第一次执行时直接编译成机器码。没有中间字节码,没有解释器。”见v8 design docs -> Dynamic Machine Code Generation @flob JS 只编译 PHP 或 Python。 V8 是一个很棒的运行时,但你给它的是纯文本脚本,而不是编译后的二进制文件。 @BaileyS 它的行为类似于解释器,但它在许多方面都像编译器一样运行,通常称为 JIT compiler。这是一个灰色地带,但我认为称其为“编译”并非没有道理。 @BaileyS 最好考虑编译和解释的实现,因为语言可以有多个。如果有人以某种方式为 PHP 编写了一个非常快速的编译器,那么语言仍然是相同的。所以我想我同意添加新编译器不会重新分类语言,但不是因为 JIT 编译器不是真正的编译器。【参考方案3】:可以把 JavaScript 做成同步语言吗?
Javascript 不是“异步语言”;相反,node.js 有很多异步的API。异步性是 API 而不是语言的属性。在 javascript 中可以轻松地创建和传递函数,这使得传递回调函数变得很方便,这是在异步 API 中处理控制流的一种方式,但是 javascript 本身并没有什么异步的。 Javascript 可以轻松支持同步 API。
为什么 node.js 是异步的?
Node.js 偏爱异步 API,因为它是单线程的。这允许它有效地管理自己的资源,但要求长时间运行的操作是非阻塞的,并且异步 API 是一种允许通过大量非阻塞操作控制流的方法。
【讨论】:
以上是关于为啥 node.js 是异步的?的主要内容,如果未能解决你的问题,请参考以下文章
在释放 zalgo 的 Node.js 设计模式中,为啥异步路径是一致的?