并发模型与Event Loop

JavaScript的并发模型基于"event loop"。这个模型与其他在C或者Java中的模型着实不同。

运行时概念

下面的内容解释了一个理论上的模型。现代JavaScript引擎着重实现和优化了描述的几个语义。

可视化描述

函数调用形成了一个frames的栈。

function f(b){
  var a = 12;
  return a+b+35;}function g(x){
  var m = 4;
  return f(m*x);}g(21);

调用g的时候,创建了第一个frame,包含了g的变量以及其本地变量。当 g 调用 f 的时候,第二个frame就被创建、并置于第一个frame之上,包含了f的参数和本地变量。当f返回时,最上层的frame就出栈了(剩下g函数调用的frame)。当g返回的时候,栈就空了。

对象被分配在一个堆中,一个用以表示一个内存中大的未被组织的区域。

队列

一个JavaScript运行时包含了一个待处理的消息队列。每一个消息都与一个函数相关联。当栈为空时,从队列中取出一个消息进行处理。这个处理过程包含了调用与这个消息相关联的函数(以及因此而创建的一个初始栈结构)。当栈再次为空的时候,也就意味着消息处理结束。 

事件循环

之所以称为 事件循环,是因为它经常被用于类似如下的方式来实现:

while(queue.waitForMessage()){
  queue.processNextMessage();
}

如果当前没有任何消息,queue.waitForMessage 会同步等待消息到来。

"执行至完成"

每一个消息执行完成后,其它消息才会被执行。当你分析你的程序时,这点提供了一些优秀的特性,包括当一个函数运行时,它不能被取代且会在其它代码运行前先完成(而且能够修改这个函数控制的数据)。这点与C语言不同。例如,C语言中当一个程序在一个线程中运行时,它可以在任何点停止且可以在其它线程中运行其它代码。

这个模型的一个缺点在于当一个消息的完成耗时过长,网络应用无法处理用户的交互如点击或者滚动。浏览器用“程序需要过长时间运行”的对话框来缓解这个问题。一个比较好的解决方案是使消息处理变短且如果可能的话,将一个消息拆分成几个消息。

添加消息

在浏览器里,当一个事件出现且有一个事件监听器被绑定时,消息会被随时添加。如果没有事件监听器,事件会丢失。所以点击一个附带点击事件处理函数的元素会添加一个消息。其它事件亦然。

调用 setTimeout 函数会在一个时间段过去后在队列中添加一个消息。这个时间段作为函数的第二个参数被传入。如果队列中没有其它消息,消息会被马上处理。但是,如果有其它消息,setTimeout消息必须等待其它消息处理完。因此第二个参数仅仅表示最少的时间 而非保证的时间。

几个运行时(Runtime)互相通信

一个web worker或者一个跨域的iframe都有它们自己的栈,堆和消息队列。两个不同的运行时只有通过postMessage方法进行通信。这个方法会给另一个运行时添加一个消息如果后者监听了message事件。

绝不阻塞

一个很有趣的事件循环(event loop)模型特性在于,Javascript跟其它语言不同,它永不阻塞。处理I/O (input/output)通常由事件或者回调函数进行实现。所以当一个应用正等待IndexedDB的查询的返回或者一个XHR(译者注:用于Ajax)的请求返回时,它仍然可以处理其它事情例如用户输入。

例外是存在的,如alert或者同步XHR,但避免它们被认为是最佳实践。注意的是,例外的例外也是存在的(但通常是实现错误而非其它原因)。


你可能感兴趣的:(并发模型与Event Loop)