JavaScript执行引擎未指定?

我最近开始学习JavaScript。 我一直在使用Node.js和Angular创建几个月的应用程序。

令我困惑的主要方面之一是如何在JavaScript中编写异步代码,我不必担心线程同步,竞争条件等问题。

所以,我发现了一些有趣的文章([1],[2]),它们解释了我可以如何保证我写的任何代码总是由单个线程执行。 底线,我所有的异步代码只是安排在事件循环中的某个点执行。 这听起来非常像操作系统调度程序可以在具有单个处理器的机器上工作,其中每个进程计划在有限的时间内使用处理器,这给我们带来了虚假的并行感。 回调将像中断一样。

这些文章没有提供任何特定的参考,所以我认为关于JavaScript执行引擎如何工作的最佳来源当然应该是语言规范,所以我给了我最新的EcmaScript 5.1副本。

令我惊讶的是,我发现这种执行行为并没有在那里指定。 怎么来的? 这看起来像是在浏览器和节点中的所有JavaScript执行引擎中完成的基本设计选择。 有趣的是,我还没有找到一个地方,这是为任何特定的引擎指定的。 事实上,我不知道人们是如何看待这种事情的方式,在上面引用的书籍和博客中如此明确地肯定这一点。

所以,我有一套我认为有趣的问题。 我将不胜感激任何提供见解,评论或简单参考的答案,指引我在正确的方向上理解以下内容:

  • 由于EcmaScript没有指定JavaScript执行引擎应该与事件循环一起工作,所以JavaScript的实现怎么可能以这种方式工作,不仅在浏览器中,而且在Node.js中?
  • 这是否意味着我可以实现一个兼容EcmaScript的新JavaScript引擎,它实际上提供了真正的多线程功能,如同步锁定,条件等功能?
  • 如果我想执行强烈的CPU绑定任务,使用事件循环的此执行模型是否会阻止我利用多核? 我的意思是,我一定可以把任务分成几块(正如其中一篇文章中所解释的那样),但这仍然是连续执行的,而不是并行执行。 那么,JavaScript引擎如何利用多核来运行我的代码呢?
  • 你是否知道任何其他有信誉的来源,对于任何特定的JavaScript引擎实现的这种行为是正式指定的?
  • 如果我们不能假设一些关于执行环境的东西,那么代码如何可以在库和引擎之间移植?
  • 它看起来像太多的问题,可能使这个帖子太宽泛而无法回答。 如果它关闭,我会尝试在不同的线程中询问他们。 但是他们都是围绕这样一个事实,即我想更好地理解为什么JavScript和Node是用事件循环来设计的,并且如果在某处(除了浏览器源代码之外)指定了我可以阅读并获得对设计和决策的更深入理解在这里更重要的是,要确切地知道写书和写文章的人们的信息来源是什么。


    有一些假设/你引用你的结论很差。 他们之中有一些是:

  • ECMAScript ECMA-XXX vs JavaScript vs JavaScriptEngine:

    ECMAscript是由ECMA International提供的语言规范。 JavaScript是符合ECMAscript的最广泛使用的Web语言。 对于大多数情况下,ECMAScript和JavaScript是同义词(请记住有ActionScript)。 JavaScriptEngine是JavaScript语言代码的实现(解释器)。 这是一个与肉体和骨头相关的程序,与ECMAScript不同,它只描述JavaScript的最终目标和行为,JavaScript是使用ECMAScript标准的代码。 您会发现引擎不仅仅符合ECMAScript标准。 它们处于规范/实施范围的末端。 这个例子是ECMA-262 / JavaScript / V8。

  • 浏览器中的事件循环与node.JS中的事件循环(JSEngine vs JSEnvironment):

    这看起来像是在浏览器和节点中的所有JavaScript执行引擎中完成的基本设计选择。

    如果您使用node.JS,则可能使用了核心库fs / net / http。 这些使用事件发射器,它们与libuv提供的事件循环挂钩。 这是对JavaScriptEngine V8的扩展,形成了node.JS平台。 这里的事件循环涉及线程,套接字,文件或抽象请求等对象。 但这件事并不是来自这里。 它最早在浏览器中使用。 浏览器实现了一个需要使用HTML元素的事件的DOM。 查看DOM规范,并为Mozilla实现了一个。 他们使用事件并需要建立在JSEngine之上的事件循环以供浏览器使用。 Chrome为其嵌入的V8引擎添加了DOM接口。

    是的,你会觉得这很常见,因为所有浏览器中都有必要的DOM API。 节点开发人员在libuv的帮助下将这一新颖的处理过程提交给了服务器,这为服务器上所需的低级操作提供了非阻塞的异步抽象。 正如已经指出的,并非所有的服务器框架都使用事件循环。 以Rhino为例,它实际上使用Java Classes作为文件,套接字(一切)。 如果实际使用核心Java IO,则文件操作是同步的。

  • 现在按顺序回答你的问题:

  • 在上面的第2点中解释

  • 是的你可以。 看看犀牛,还有很多其他的。 它可能在节点中,但是节点适合作为一个高性能的网络服务器,并且可能违背它的禅宗。

  • 就像我说的事件循环坐落在JSEngine上。 这是一种设计模式,它最适合IO。 多线程设计在CPU负载过高时效果更好。 如果您想在node.JS中使用多个核心,请查看集群模块。 对于浏览器你有网络工作者

  • 这从发动机到发动机各不相同。 以及它如何嵌入。 浏览器将具有DOM并因此具有事件循环。 服务器可以变化。 检查他们的规格。

  • 对于浏览器来说,可以在很大程度上使它们在它们之间移植。 没有承诺的服务器。


  • 事件循环与javascript本身没有任何关系,它是环境的一部分,而不是js引擎。 由于JavaScript主要是为了操作用户界面而设计的,因此它在事件循环中被大量使用。 但事件循环是UI实现的一部分,不仅在JavaScript中,而且在任何语言中。

  • 是的你可以。 但它不会只是引擎,更像环境/平台。 我认为(但不太确定)你可以在Rhino中使用线程和相关的东西。

  • 是的,它确实。 在节点中,这通常通过产生更多的进程来解决,并且在浏览器中您可以使用WebWorkers。

  • 我无法想象一个更好的来源,然后规范。 如果有东西不在那里,它只是不是JavaScript的一部分(又名EcmaScript)


  • 我今天花了很多时间试图找到我自己的问题的答案,在这里留下的一些评论和其他答案的指导下。 我分享我的发现,以防其他人认为它们有用。

    JavaScript中针对浏览器的事件驱动设计

    以这种方式设计JavaScript的决定似乎主要与DOM Event Architecture的要求有关。 在本规范中,我们可以找到与事件顺序和事件循环的实现相关的明确要求。 HTML5规范更进一步,明确定义条款并声明事件循环实现的具体要求。

    这当然必须驱动浏览器中JavaScript执行引擎的设计。 在这篇由Opera发布的JavaScript定时和同步中,我们可以清楚地看到这些需求是Opera浏览器设计背后的推动力。 同样在Mozilla的这篇名为Concurrency Model和Event Loop的另一篇文章中,我们可以找到由Mozilla实现的相同事件驱动设计概念的清晰解释(尽管文档看起来已经过时)。

    使用事件循环来处理这种应用程序并不新鲜。

    处理用户输入是交互式编程最复杂的一个方面。 应用程序可能对多个输入设备(例如鼠标和键盘)敏感,并且可能在多个输入设备(例如不同窗口)之间多路复用这些设备。 管理这种多对多映射通常位于用户界面管理系统(UIMS)工具包的省份。 由于大多数UIMS都是以顺序语言实现的,所以他们必须采用各种技术来模拟必要的并发。 通常,此工具包使用事件循环来监视输入事件流,并将事件映射到由应用程序员提供的回调函数(或事件处理程序)。 - Jonh H. Reppy - ML中的并发编程

    其他着名的UI工具包(如Java Swing和Winforms)中使用了事件循环。 在Java中,所有UI工作必须在WinForms中的EventDispatchThread中完成。所有UI工作必须在创建Window对象的线程内完成。 因此,即使这些语言支持真正的多线程,他们仍然需要在单个执行线程中运行所有UI代码。

    Douglas Crockford在这个伟大的视频中称为Loopage(值得关注)解释了JavaScript中事件循环的历史。

    JavaScript中针对节点的事件驱动设计

    现在,使用Node.js的事件驱动设计的决定不太明显。 克罗克福德在上面分享的视频中给出了一个很好的解释。 而且,在书中,JavaScript的过去,现在和未来,其作者Axel Rauschmayer说:

    2009-Node.js,服务器上的JavaScript。 Node.js允许您实现在负载下运行良好的服务器。 为此,它使用事件驱动的非阻塞I / O和JavaScript(通过V8)。 Node.js的创建者Ryan Dahl提到了选择JavaScript的下列原因:

  • “因为它是裸露的,并不包含I / O API。”[Node.js因此可以引入自己的非阻塞API。]
  • “Web开发人员已经使用它。”[JavaScript是一种广为人知的语言,特别是在Web环境中。]
  • “DOM API是基于事件的。 每个人都已经习惯于在没有线程和事件循环的情况下运行。“[Web开发人员并不害怕回调。]
  • 因此,看起来Node.js的创建者Ryan Dahl在浏览器中考虑了JavaScript的当前设计,以决定哪些应该是Node.js的非阻塞事件驱动解决方案的实现。

    Node.js的最新实现似乎使用了一个名为libuv的库,用于实现这类应用程序。 这个库是节点设计的核心部分。 我们可以在文档中找到事件循环的定义。 显然,这在Node.js的当前实现中起着重要的作用。

    关于其他EcmaScript兼容引擎

    EcmaScript规范没有提供关于如何在JavaScript中处理并发的要求。 因此,这是由语言的执行决定的。 其他并发模型可以很容易地使用,而不会使实现与标准不兼容。

    我找到的最好的两个例子是为Oracle为JDK8创建的新的Nashorn JavaScript Engine和由Mozilla创建的Rhino JavaScript Engine。 它们都兼容EcmaScript,它们都允许创建Java类。 这些引擎中不需要使用事件驱动编程来处理并发。 这些引擎可以访问Java类库,并且由于它们运行在JVM之上,因此它们可能访问此平台中提供的其他并发模型。

    请考虑以下JavaScript示例,该示例指南演示了如何使用Rhino JavaScript。

    print(x); // Global print function prints to the console
    version(170); // Tell Rhino we want JS 1.7 language features
    load(filename,...); // Load and execute one or more files of JavaScript code
    readFile(file); // Read a text file and return its contents as a string
    readUrl(url); // Read the textual contents of a URL and return as a string
    spawn(f); // Run f() or load and execute file f in a new thread
    runCommand(cmd, // Run a system command with zero or more command-line args
    [args...]);
    quit() // Make Rhino exit
    

    你可以看到一个新的线程可以被派生到一个独立的执行线程中运行一个JavaScript文件。

    关于事件驱动设计,多核和真并发

    关于此主题的最佳解释来自JavaScript The权威指南。 在本书中,David Flanagan解释道:

    客户端JavaScript的基本功能之一是它是单线程的:浏览器永远不会同时运行两个事件处理程序,例如,在事件处理程序运行时它永远不会触发定时器。 同时更新应用程序状态或文档是不可能的,客户端程序员不需要考虑甚至理解并发编程。 一个必然结果是,客户端JavaScript函数不能运行得太长,否则它们会绑定事件循环,并且Web浏览器将不响应用户输入。 这就是Ajax API总是异步的原因,以及客户端JavaScript无法用于加载JavaScript库的简单同步load()require()函数的原因。

    Web Workers规范非常小心地放宽了客户端JavaScript的单线程要求。 它定义的“工人”是有效的并行执行线程。 Web工作人员生活在一个自包含的执行环境中,但是无法访问Window或Document对象,只能通过异步消息传递与主线程通信。 这意味着DOM的并发修改仍然不可行,但这也意味着现在有一种方法可以使用同步API并编写长时间运行的函数,这些函数不会阻塞事件循环并挂起浏览器。 创建一个新的工作人员不是像打开一个新的浏览器窗口这样的重量级操作,但工作人员也不是轻量级的线程,而且创建新的工作人员来执行微不足道的操作是没有意义的。 复杂的Web应用程序可能会发现创建数十名工作人员非常有用,但具有数百或数千名工作人员的应用程序不太可能实用。

    关于Node.js真正的并行性呢?

    Node.js是一种快速发展的技术,也许这就是为什么很难找到最新的意见。 但基本上,因为它遵循与浏览器相同的事件驱动模型,所以不可能简单地编写一段代码,并期望它能利用服务器中的多核。 由于Node.js是使用非阻塞技术实现的,所以我们可以假设每次我们做某种形式的I / O时(即读取文件,通过套接字发送内容,写入数据库等) ,节点引擎可能会产生多个线程,并可能利用内核,但是我们的代码仍然会连续运行。

    现在看起来,node.js集群是解决这个问题的方法。 也有一些像Node Worker这样的库似乎在节点中实现Web Worker概念。 这些库基本上让我们在node.js中产生新的独立进程。 (虽然我还没有尝试过)。

    可移植性如何?

    就并发模型而言,看起来没有办法,我们可以保证所有这些库在所有环境中都能很好地运行。

    尽管在浏览器领域他们似乎都有相似的工作,并且由于Node.js在事件循环中运行,许多事情仍然可以工作,但并不能保证这可以在其他引擎中工作。 我想这可能是EcmaScript与其他更广泛的规范(如定义Java虚拟机或CLR​​的规范)相比的缺点之一。

    也许以后会有些事情变得标准化 在未来的EcmaScript中,更多的并发想法正在被讨论。 请参阅EcmaSript Wiki:Strawman建议沟通事件循环并发和分发

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

    上一篇: JavaScript Execution Engine Unspecified?

    下一篇: Link and execute external JavaScript file hosted on GitHub