JavaScript执行引擎未指定?

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

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

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

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

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

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

  • 由于EcmaScript没有指定JavaScript执行引擎应该与事件循环一起工作,所以JavaScript的实现似乎可以这样工作,不仅在浏览器中,而且在Node.js中呢?
  • 这是否意味着我可以实现一个新的与EcmaScript兼容的JavaScript引擎,实际上它提供了真正的multithreadingfunction,如同步locking,条件等function?
  • 使用事件循环的这个执行模型是否会阻止我利用多核,如果我想要执行一个强烈的CPU绑定任务? 我的意思是,我一定可以把任务分成几块(正如其中一篇文章中所解释的那样),但这仍然是连续的,而不是并行的。 那么,JavaScript引擎如何利用多核来运行我的代码呢?
  • 你知道任何其他有信誉的来源,对于任何特定的JavaScript引擎实现的这种行为是正式指定的吗?
  • 如果我们不能假设一些关于执行环境的东西,那么代码如何能够在库和引擎之间移植呢?

这个问题看起来太多了,或许这个post太宽泛,无法回答。 如果closures,我会试着用不同的线索问他们。 但是他们都是围绕这样一个事实,即我想更好地理解为什么JavScript和Node是用事件循环来devise的,如果在某处(除了浏览器源代码之外)指定了我可以阅读并深入理解devise和决策在这里,更重要的是,要确切地知道什么是写书的人的信息来源。

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

  1. 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。

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

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

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

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

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

  • 在上面的第2点中解释

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

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

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

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

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

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

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

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

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

JavaScript中的浏览器事件驱动devise

以这种方式deviseJavaScript的决定似乎大部分与DOM Event Architecture的要求有关。 在本规范中,我们可以find与事件顺序和事件循环的实现相关的明确要求 。 HTML5规范更进一步,明确地定义了术语,并陈述了事件循环实现的具体要求 。

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

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

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

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

道格拉斯· 克罗克福德 (Douglas Crockford)在这个伟大的video中(在这个值得关注的video)中解释了JavaScript中事件循环的历史。

JavaScript中针对节点的事件驱动devise

现在,使用Node.js的事件驱动devise的决定是不太明显的。 Crockford在上面分享的video中给出了一个很好的解释。 而且,在“ JavaScript的过去,现在和未来 ”一书中,其作者Axel Rauschmayer说:

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

  • “因为它是裸露的,并不包含I / O API。”[Node.js因此可以引入自己的非阻塞API。]
  • “Web开发人员已经使用它了。”[JavaScript是一种广为人知的语言,特别是在Web环境中。]
  • “DOM API是基于事件的。 每个人都已经习惯于在没有线程和事件循环的情况下运行。“[Web开发人员并不害怕callback。]

所以,看起来Node.js的创build者Ryan Dahl在浏览器中考虑了JavaScript的当前devise,以决定哪个应该是Node.js的非阻塞事件驱动解决scheme的实现。

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

关于其他EcmaScript兼容的引擎

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

我find的最好的两个例子是为Oracle为JDK8创build的新的Nashorn JavaScript引擎 ,以及由Mozilla创build的Rhino JavaScript引擎 。 它们都兼容EcmaScript,并且都允许创buildJava类。 这些引擎中不需要使用事件驱动编程来处理并发。 这些引擎可以访问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文件。

关于事件驱动的devise,多核和真正的并发

我在这个主题上find的最好的解释来自JavaScript The权威指南 。 在这本书中,大卫·弗拉纳根解释说:

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

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

什么关于Node.js真平行?

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

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

可移植性如何?

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

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

也许以后会有一些东西被标准化 在未来的EcmaScript中,更多的并发思想正在被讨论。 请参阅EcmaSript Wiki:Strawmanbuild议沟通事件循环并发和分发