JavaScript并发模型和事件循环

  1、简介

  JS有一个基于“事件循环”的并发模型。这个模型和其他语言(如C和Java)的模型不太一样。

  下图描述的是一个理论模型,现代JS引擎在此基础上实现和进行了很多优化:

  2、模型详细介绍

  1)相关概念

  (1)栈Stack:如下例,在调用g之前,以及在g返回之后,栈都为空。

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

  (2)堆:这里不介绍。

  (3)队列:待处理消息的列表,每条消息都关联一个回调函数。

  JavaScript是单线程的(多进程/多线程往往有更大的内存开销、上下文切换开销和数据竞争问题等),这意味着需要有一个队列保存异步执行的代码。

  I、入队操作。例如,某个按钮被按下时,它的事件处理器代码被添加到队列;接收到某个Ajax响应时,回调函数的代码被添加到队列;对于定时器,当指定时间过去后将其回调函数的代码添加到队列。

  II、出队操作。队列中没有任何代码是立刻执行的,但一旦进程空闲(此时栈为空)则尽快执行。进程每次从队列中取出一条消息并调用其回调函数(异步操作的结果通过回调函数获得)。这使得栈变为非空。当栈再次变为空时,表示该消息处理完毕。

  (4)事件循环。得名于它通常的实现方式:

// 所谓事件循环,就像代码从一个循环中不断取出而运行一样
while (queue.waitForMessage()) { queue.processNextMessage(); }  // waitForMessage():当前没有消息时执行同步等待

  维基百科的定义:event loop/message dispatcher/message loop/run loop/...是一个程序结构,用于等待和分发(dispatch)事件或消息。

  node.js依赖于libev提供的事件循环。

  2)模型特点:

  (1)完整运行(Run-to-completion):每条消息处理完成后,再处理其他消息。

  进入一个函数后,只有在它完整运行后才会“切换”到其他代码,从而无需担心函数操作的数据被意外修改。

  不足之处在于,如果一条消息处理时间过长,则Web应用程序无法响应用户的交互操作,浏览器将提示"a script is taking too long to run"。一个好的做法是缩短消息处理过程,并尽可能把一条消息“切分”为多条。

  例子:

setTimeout(function cb() { console.log('5 seconds timeout'); }, 5000);
setTimeout(function cb() { console.log('9 seconds timeout'); }, 9000);
setTimeout(function cb() { console.log('7 seconds timeout'); }, 7000);
setTimeout(function cb() { console.log('3 seconds timeout'); }, 3000);

for(var start = +new Date; +new Date - start <= 10000; ) {}  // “模拟”睡眠

  运行大约10s后,输出:

3 seconds timeout
5 seconds timeout
7 seconds timeout
9 seconds timeout

  (2)从不阻塞(也有例外,如alert或同步XHR,但最好避免使用它们):I/O的处理一般借助于事件和回调函数,因此当应用程序在等待一个IndexedDB查询或一个XHR请求返回时,它仍可以处理其他事情。

 

  参考资料:

  Concurrency model and Event Loop

  《JavaScript异步编程》

  《JavaScript高级程序设计》

 

 

不断学习中。。。

posted on 2016-09-27 17:33  han'er  阅读(3869)  评论(0编辑  收藏  举报

导航