异步

134 阅读4分钟

分块的程序

javascript程序写在.js文件中,但这个程序几乎一定是由多个块构成的,这些块中只有一个是现在执行,其余的则是在将来执行,最常见的块单位是函数

程序中将来要执行的任务不一定会在现在正在运行的部分运行结束后立即执行,换句话说,现在无法完成的任务将会异步完成

从现在到将来这一期间,最简单的实现方法就是使用回调函数,举例!

        function now() {
            return 21;
        }
        function later() {
            answer = answer * 2;
            console.log(answer);
        }
        var answer = now();
        setTimeout(later, 1000)

这个程序有两个块:现在执行的部分,将来执行的部分

现在:

        function now() {...}
        function later() {...}
        var answer = now();
        setTimeout(later, 1000)

将来:

            answer = answer * 2;
            console.log(answer);

任何时候,只要把一段代码包装成一个函数,并指定它在响应某个事件(如定时器,点击事件等)时执行,就是使用了异步

事件循环

javascript引擎本身所做的只是在需要的时候,在给定的任意时刻执行程序中的单个代码块

那么是谁的需要?

javascript引擎并不是独立运行的,它运行在宿主环境中,通常是浏览器。这些宿主环境都有一个共同点(线程),即它们都提供了一个机制来处理程序中多个块的执行,执行每个块时都调用javascript引擎,这种机制被称为事件循环,换句话说,javascript引擎只是个按需执行代码片段的环境,事件调度总是由包含它的环境进行

注意!setTimeout()并没有把回调函数挂到事件循环队列中,它所做的只是设定定时器,当定时器到时后,环境会把回调函数放在事件循环中,这样在未来某个时刻的tick会摘下并执行这个回调

并行线程

异步和并行经常被混为一谈,但其实它们的意义是完全不同的,异步是关于现在和将来的时间间隙,并行是能够同时发生的事情

并行计算最常见的工具是进程和线程,进程和线程独立运行,并可能同时运行,在不同的处理器,甚至不同的计算机上,但多个线程能够共享单个进程内存。与之相对的,事件循环把自身工作分成一个个任务并顺序运行,不允许对共享内存的并行访问和修改。并行和顺序执行可以共存

如果在并行系统中,同一个程序中可能有两个不同的线程在运转,这时可能会得到不确定的结果,举例!

        var a = 20
        function foo() {
            a = a + 1
        }
        function bar() {
            a = a * 2
        }
        ajax("........",foo)
        ajax("........",bar)

根据javascript的单线程运行特性,foo和bar的运行会有先后顺序,会导致最终结果不一样,这种函数顺序的不确定性就是通常所说的竞态条件

并发

两个或多个进程(任务)同时执行就出现了并发,不管组成它们的单个运算是否并行执行,可以把并发看作任务级的并行,与运算级的并行相对

任务

任务队列,是挂在事件循环队列的每个tick之后的一个队列,在事件循环的每个tick中,出现的异步动作不会导致一个完整的新事件添加到事件循环队列中,而会在当前tick的任务队列末尾添加一个项目(任务),类似插队

小结

javascript程序总是至少分为两个块,第一块现在运行,下一块将来运行,以响应某个事件,尽管程序是一块块执行的,但它们共享对程序作用域和状态的访问,所以对状态的修改都是在之前累计的修改之上进行的

一旦有事件需要运行,事件循环就会运行,直到队列清空,事件循环的每一轮称为一个tick,用户,IO和定时器会向事件队列中加入事件,任意时刻,一次只能从队列中处理一个事件,执行事件的时候,可能直接或间接的引发一个或多个后续事件。

并行是指两个或多个事件链随时间发展交替执行,以至于就像是同时运行