JavaScript 单线程与异步

65    2017-08-14 10:10

很长一段时间里,我都对 “JavaScript 单线程、异步”都没有一个很好的理解,觉得这句话本身就是矛盾的, 既然是单线程则意味着所有代码都是顺序执行的,而异步就代表有另外的线程处理这异步的程序,所以又怎么能说JavaScript是单线程呢?

JavaScript 单线程有它的应用场景历史原因

借用阮一峰的例子可以很精简的说明单线程的好处: “单线程与之用途有关,单线程能够保证一致性,如果有两个线程,一个线程点击了一个元素,另一个删除了一个元素,浏览器以哪一个为准?”。

单线程与异步

  看过很多关于 JavaScript 单线程与异步的介绍,大多数都引用这一类例子来说明:单线程就是排队,前一个任务不完成后一个就不能开始,也就是常说的串行。 饭店点餐,所有人排队,A顾客点完之后,厨房开始做,做完后A领到餐后,B再开始点餐,厨房又开始做…周而复始,这就是同步,点餐的时候厨房是闲着的,做菜的时候前台是闲着的。即单线程。所有人都能够很好的理解,这样做效率太低了!

  换个思路,A点完餐后,到一旁等着,服务员将菜单递给厨房,厨房开始做,然后继续服务B顾客,当A顾客的餐做好之后,A来队伍里插个队,把餐领走。 听到这里,效率高了很多嘛,整个餐厅没有浪费一点工作力,大家都各司其事。然后作者就下了结论,这就是 JavaScript 的单线程与异步。

  这样的说法长期以来,我都是接受的,因为几乎所有的网上教程都是这样讲的。偶尔,思考到这样一个问题:前台服务员在点餐,厨房在做饭,这难道不是两个线程吗?那凭什么说JavaScript是单线程的?

常说的单线程其实指的是主线程

主线程

  JavaScript引擎负责解析,执行JavaScript代码,但它并不能单独运行,它需要一个宿主环境,熟知的宿主环境有“浏览器”和“Node.js”, 单线程是宿主环境没有给 JavaScript 执行过程中创建新线程的能力导致的,也称之为“主线程”。也就意味着我们(开发者)写的所有代码,都在 这一条主线程中执行。从这一点可以得出结论 JavaScript 是阻塞的:只要有一个地方发生阻塞,就会导致整个应用瘫痪。

异步

  那么异步又是怎么回事呢?以 Node.js I/O 为例子

io 
图1 io

  这里的IO包括,网络的IO操作以及文件异步IO处理。这部分由C/C++编写的Libuv库完成,从图中可以看出 Node.js 内部的异步方法均采用了__多线程机制__。 我们所谓的单线程,其实就是我们(开发者)写的 JavaScript 代码运行的线程。

事件循环(Event Loop)

  那异步和单线程之间的桥梁又是什么呢?这里要引入一个概念:事件循环

eventloop
图2 eventloop

  简单的说就是,存在一个事件队列,线程在执行任务时,会从队列中取出并执行,如果是a一个异步的操作,则交给异步的线程去处理,b任务不用等a任务结束,就已经开始执行了。   当异步操作结束之后,会将异步的回调函数放入队列中,再继续执行。

event 
图2 event

  总结起来就是,主线程是同步的,但当遇到阻塞的IO操作时,将该任务交给别人去做(相当于外包),做完之后,主线程接着处理外包返回的结果。

  光说无意,看一段代码:用setTimeout来模拟异步操作。

var a = 1;setTimeout(function(){a=2},0);console.log(a);

思考一下:打印出来的a是“1”还是“2”呢?

梳理一下单线程的执行, α、定义一个变量 β、一个异步操作,0秒之后放入队列中去执行   γ、执行打印a变量  

这里就在于到底是步骤β先执行还是步骤γ先执行: 打印操作是定义完 α 就开始执行了,而对 a=2 是在队列中需要γ执行完才开始执行。 无论 setTimeout 定义的是 1000ms 还是 0ms,都得等前面的事件执行结束才能被执行。 所以这里打印的结果是 1。从这里我们也可以佐证,setTimeout 这个方法是不准确的,不可靠的,它并不能保证内部的函数在约定时间执行。 再看下面这一段代码:

var a = 1;setTimeout(function(){a=2},1000);while(true){
    console.log(a);}

想象一下,这段代码最终能够打印出 a = 2 这个结果吗? 不能!因为while()这里永远为真,这里的事件永远都执行不完,所以队列之后的代码也不可能被执行,这也验证了 JavaScript 是单线程的这一说法。

小结

综上所述,简单说明了 JavaScript 的单线程和异步,以上内容属个人理解和网上学习总结,如有错误,欢迎指正共勉。 从文中列举的两个例子可以说明,虽然 JavaScript 可以通过异步的方式解决 IO 性能问题,但影响效率的还是在主线程上面,如果主线程中有太多的阻塞代码, 再多的异步也是解决不了性能问题。

对了,如果你有在浏览器中执行以上代码,想必电脑的电风扇已经在高速运转了,看看任务管理器,是不是cup已经爆棚了… 赶紧结束进程吧,你刚刚执行的可是一段永为真的代码呀!知道堵塞的严重性了吧。【逃】

参考




  • 请先登录 ~\(≧▽≦)/~,再发表评论 /(ㄒoㄒ)/~~
  • 评论内容不要超过233个字符 (⊙o⊙)哦
  • 请注意单词拼写,以及中英文排版,参考此页