js的单线程和异步
yyjo3358
8年前
<h3><strong>前言</strong></h3> <p>说到js的单线程(single threaded)和异步(asynchronous),很多同学不禁会想,这不是自相矛盾么?其实,单线程和异步确实不能同时成为一个语言的特性。js选择了成为单线程的语言,所以它本身不可能是异步的,但js的宿主环境(比如浏览器,Node)是多线程的,宿主环境通过某种方式(事件驱动,下文会讲)使得js具备了异步的属性。往下看,你会发现js的机制是多么的简单高效!</p> <h3><strong>说说浏览器</strong></h3> <p>js是单线程语言,浏览器只分配给js一个主线程,用来执行任务(函数),但一次只能执行一个任务,这些任务形成一个任务队列排队等候执行,但前端的某些任务是非常耗时的,比如网络请求,定时器和事件监听,如果让他们和别的任务一样,都老老实实的排队等待执行的话,执行效率会非常的低,甚至导致页面的假死。所以,浏览器为这些耗时任务开辟了另外的线程,主要包括http请求线程,浏览器定时触发器,浏览器事件触发线程,这些任务是异步的。下图说明了浏览器的主要线程。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/214036ab0426d6028670bd7f808e21af.png"></p> <h3><strong>再说说任务队列</strong></h3> <p>刚才说到浏览器为网络请求这样的异步任务单独开了一个线程,那么问题来了,这些异步任务完成后,主线程怎么知道呢?答案就是回调函数,整个程序是事件驱动的,每个事件都会绑定相应的回调函数,举个栗子,有段代码设置了一个定时器</p> <pre> <code class="language-javascript">setTimeout(function(){ console.log(time is out); },50);</code></pre> <p>执行这段代码的时候,浏览器异步执行计时操作,当50ms到了后,会触发定时事件,这个时候,就会把回调函数放到任务队列里。整个程序就是通过这样的一个个事件驱动起来的。</p> <p>所以说,js是一直是单线程的,浏览器才是实现异步的那个家伙。</p> <h3><strong>说回主线程</strong></h3> <p>js一直在做一个工作,就是从任务队列里提取任务,放到主线程里执行。下面我们来进行更深一步的理解。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/bda084269322ee343a478ac334e7a385.png"></p> <p>图片来自Philip Roberts的演讲《Help, I'm stuck in an event-loop》非常深刻!</p> <p>我们把刚才了解的概念和图中做一个对应,上文中说到的浏览器为异步任务单独开辟的线程可以统一理解为WebAPIs,上文中说到的任务队列就是callback queue,我们所说的主线程就是有虚线组成的那一部分,堆(heap)和栈(stack)共同组成了js主线程,函数的执行就是通过进栈和出栈实现的,比如图中有一个foo()函数,主线程把它推入栈中,在执行函数体时,发现还需要执行上面的那几个函数,所以又把这几个函数推入栈中,等到函数执行完,就让函数出栈。等到stack清空时,说明一个任务已经执行完了,这时就会从callback queue中寻找下一个人任务推入栈中(这个寻找的过程,叫做event loop,因为它总是循环的查找任务队列里是否还有任务)。</p> <h3><strong>借以解释几个容易困惑的问题</strong></h3> <ol> <li> <p>setTimeout(f1,0)是什么鬼</p> <p>这个语句最大的疑问是,f1是不是立刻执行?答案是不一定,因为要看主线程内的命令是否已经执行完了,如下代码:</p> <pre> <code class="language-javascript">setTimeout(function(){ console.log(1); },0); console.log(2);</code></pre> 这段代码的输出结果是2,1。因为执行setTimeou后,会立即把匿名函数放到callback queue里面等待主线程的召唤,但这个时候stack里面并不是空的,因为还有一句console.log(2)。等到执行完console.log(2)后,才通过event loop把匿名函数放到stack里面。所以setTimeout(f1,0)这个语句并不是没有意义,如果f1是很耗时的任务,那就应该把任务放到callback queue里面,等到主程序执行完后再执行。</li> <li>Ajax请求是否异步<br> 了解完上文内容,我们就知道了,ajax请求内容的时候是异步的,当请求完成后,会触发请求完成的事件,然后把回调函数放入callback queue,等到主线程执行该回调函数时还是单线程的。</li> <li> <p>界面渲染线程是单独开辟的线程,是不是DOM一变化,界面就立刻重新渲染?</p> <p>如果DOM一变化,界面就立刻重新渲染,效率必然很低,所以浏览器的机制规定界面渲染线程和主线程是互斥的,主线程执行任务时,浏览器渲染线程处于挂起状态。</p> </li> </ol> <h3><strong>如何利用浏览器的异步机制</strong></h3> <p>我们已经知道,js一直是单线程执行的,浏览器为几个明显的耗时任务单独开辟线程解决耗时问题,但是js除了这几个明显的耗时问题外,可能我们自己写的程序里面也会有耗时的函数,这种情况怎么处理呢?我们肯定不能自己开辟单独的线程,但我们可以利用浏览器给我们开放的这几个窗口,浏览器定时器线程和事件触发线程是好利用的,网络请求线程不适合我们使用。下面我们具体看一下:</p> <p>假设耗时函数是f1,f1是f2的前置任务。</p> <ul> <li> <p>利用定时器触发线程</p> <pre> <code class="language-javascript">function f1(callback){ setTimeout(function(){ // f1 的代码 callback(); },0); } f1(f2);</code></pre> <p>这种写法的耦合度高。</p> </li> <li> <p>利用事件触发线程</p> <pre> <code class="language-javascript">$f1.on('custom',f2); //这里绑定事件以jQuery写法为例 function f1(){ setTimeout(function(){ // f1的代码 $f1.trigger('custom'); },0); }</code></pre> <p>这种方法通过绑定自定义事件,对方法一解耦,这样可以通过绑定不同的事件,实现不同的回调函数,但如果应用这种方法过多,不利于阅读程序。</p> </li> </ul> <h3><strong>异步的好处和适合的场景</strong></h3> <p>1.异步的好处<br> 我们直接通过一个例子对同步和异步进行对比,假设有四个任务(编号为1,2,3,4),它们的执行时间都是10ms,其中任务2是任务3的前置任务,任务2需要20ms的响应时间。下面我们做下对比,你就知道怎么实现的非阻塞I/O了。</p> <p style="text-align:center"><br> <img src="https://simg.open-open.com/show/77411f142b2d357359e15b7317489a2c.png"></p> <p>2.适合的场景<br> 可以看出,当我们的程序需要大量I/O操作和用户请求时,js这个具备单线程,异步,事件驱动多种气质的语言是多么应景!相比于多线程语言,它不必耗费过多的系统开销,同时也不必把精力用于处理多线程管理,相比于同步执行的语言,宿主环境的异步和事件驱动机制又让它实现了非阻塞I/O,所以你应该知道它适合什么样的场景了吧!</p> <p><strong>参考内容:</strong></p> <p><a href="/misc/goto?guid=4959724956759873994" rel="nofollow,noindex">http://www.sohamkamani.com/blog/2016/03/14/wrapping-your-head-around-async-programming/</a></p> <p><a href="/misc/goto?guid=4959653105135446254" rel="nofollow,noindex">https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop</a></p> <p><a href="/misc/goto?guid=4959724956883047807" rel="nofollow,noindex">https://vimeo.com/96425312</a></p> <p><a href="/misc/goto?guid=4959724956971405727" rel="nofollow,noindex">http://blog.csdn.net/kfanning/article/details/5768776</a></p> <p><a href="/misc/goto?guid=4958850434773685572" rel="nofollow,noindex">http://www.ruanyifeng.com/blog/2014/10/event-loop.html</a></p> <p><a href="/misc/goto?guid=4959544615210625452" rel="nofollow,noindex">http://www.ruanyifeng.com/blog/2012/12/asynchronous%EF%BC%BFjavascript.html</a></p> <p> </p> <p>来自:http://www.cnblogs.com/woodyblog/p/6061671.html</p> <p> </p>