javascript单线程整理和理解

    异步的另外一种含义是计算机多线程的异步处理。与同步处理相对,异步处理不用阻塞当前线程来等待处理完成,而是允许后续操作,直至其它线程将处理完成,并回调通知此线程。异步与多线程与并行不是同一个概念。


    先问自己几个问题

    1. javascript是单线程的吗?
    2. javascript如果是单线程为什么能让ajax异步发送和回调请求?
    3. setTimeout一定是按照它定的时间发生的吗?
    4. 浏览器是多线程的吗?
    5. event loop是什么呢?
    6. 下面这段代码的结果是什么?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    function foo(){
    setTimeout(function(){
    console.log("inner");
    })
    console.log("outer");
    }
    for(var i = 0;i < 10;i++){
    foo();
    }

    javascript是单线程

    方便理解,可以这么想,如果js不是单线程的,那么当我们要可能在操作一个DOM的时候(比如修改,删除,添加),另一个线程可能也在对这个DOM节点进行操作。这么肯定是不行的。

    这跟javascript最初的用途是有关的,js本来设计就是为了更好交互效果,更方便操作DOM,这一特点某种程度上决定了他的单线程。

    js运行在浏览器中是单线程的。每一个window一个js线程。
    由于是单线程,那么在一个时刻只能够执行某个特定的代码,并且阻塞其他的代码。这也就意味着如果前面一个任务耗时很长的时候,后一个任务就会一直等待。
    js的单线程机制其实是:有两个队列,一个是主线程。一个是任务队列。


    浏览器不是单线程的

    js运行在浏览器中,是单线程的,每个window一个js线程,但是浏览器不是单线程的。他可能有很多线程,比如js引擎线程,界面渲染线程,浏览器事件触发线程,http请求线程。


    单线程的机制

    刚刚提到了javascript的单线程的机制其实是有两个队列,一个是主线程队列,一个是任务队列。

    之所以要有这么两个队列:

    JavaScript语言的设计者意识到,这时主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。

    于是,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。–from ruanyifeng

    同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;

    异步任务指的是,不进入主线程、而进入”任务队列”(task queue)的任务,只有”任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

    js都是先执行同步任务,再执行异步任务

    那么上面的第六个问题就很清楚了,答案是先输出10个outer,再输出10个inner。因为outer是位于主线程上,inner是位于任务队列中,js在执行的时候必须先把主线程的做完,再执行任务队列。在主线程和任务队列这两个队列里又是又是阻塞的。

    1
    2
    3
    setTimeout( function(){ while(true){} } , 100);
    setTimeout( function(){ alert('你好!'); } , 200);
    console.log("hello");

    上面这个的结果里是可以输出hello的,跟我们想的一样,但是alert的你好,是永远都没有办法出来的。原因上面说了。


    异步事件驱动

    浏览器是事件驱动的(Event driven),浏览器中很多行为是异步(Asynchronized)的,例如:鼠标点击事件、窗口大小拖拉事件、定时器触发事件、XMLHttpRequest完成回调等。当一个异步事件发生的时候,它就进入事件队列。浏览器有一个内部大消息循环,Event Loop(事件循环),会轮询大的事件队列并处理事件。例如,浏览器当前正在忙于处理onclick事件,这时另外一个事件发生了(如:window onSize),这个异步事件就被放入事件队列等待处理,只有前面的处理完毕了,空闲了才会执行这个事件。setTimeout也是一样,当调用的时候,js引擎会启动定时器timer,大约xxms以后执行xxx,当定时器时间到,就把该事件放到主事件队列等待处理(浏览器不忙的时候才会真正执行)。


    setTimeout(func, 0)一定是0s就执行吗?

    上面说了必须要等主线程的所有事件被完成时,才能够执行任务队列上的事件。
    setTimeout(func, 0)这句话,告诉了js引擎,要在0s之后吧func函数放到主事件队列里面,等待当前其他的所有的主线程任务完成了,就开始执行。之所以有用,是因为当前并没有主线程上并没有其他的任务。如果当前的主线程上有其他的任务,那么其实func的执行时间,绝对不会是0s。因为它还要排队。


    总结异步执行的运行机制

    (1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
    
    (2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
    
    (3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
    
    (4)主线程不断重复上面的第三步。
    

    总的来说就是,任务队列里的任务都是必须等主线程的执行栈空了才去执行的。我们通常说的同步也就是没有上面的异步任务。


    Event Loop是啥子?

    主线程从“任务队列”中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。


    为什么会看见下面这种现象?

    现在想想以前写xhr的时候就在书上这么看过,但是当时也没多想,虽然知道是异步没关系。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //写法一
    var req = new XMLHttpRequest();
    req.open('GET', url);
    req.onload = function (){};
    req.onerror = function (){};
    req.send();
    //写法二
    var req = new XMLHttpRequest();
    req.open('GET', url);
    req.send();
    req.onload = function (){};
    req.onerror = function (){};

    这两种写法是一样的,为什么呢?因为send是一个异步执行过程,那么他会等当前所有的非异步执行完再去执行。它被放入了任务队列。


    再来看看异步,多线程,并行的概念

    并行,一般指并行计算,是说同一时刻(注意并发是同一时间段)有多条指令同时被执行,这些指令可能执行于同一CPU的多核上,或者多个CPU上,或者多个物理主机甚至多个网络中.

    异步,与同步相对应,异步指的是让CPU暂时搁置当前请求的响应,处理下一个请求,当通过轮询或其他方式得到回调通知后,开始运行。

    线程,是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。


    总结

    了解了js单线程,知道什么是异步事件驱动,知道了有主线程的执行栈和任务队列这两个机制。还有setTimout不一定就是在它设置的时间执行呦。学习和整理自ruanyifeng的文章,感谢。