JavaScript同步和异步

同步和异步

同步

所谓同步编程,就是计算机一行一行按顺序依次执行代码,是一种典型的请求-响应模型.

当前代码比较耗时会阻塞后续代码的执行,当请求调用一个函数或者方法后,需等待其响应返回,然后执行后续代码.

类似于生活中规范的排队,即使前面的人动作很慢,后面的人也要等着.

异步

一般情况下,同步编程,代码依次执行能很好的保证程序的执行,

但是在某些场景中,比如读取文件内容,或者请求服务器接口数据,这些请求需要根据返回的数据内容,来执行后续操作,

等待数据返回的时间中,JavaScript是不能处理页面交互,滚动等操作的,

所以需要异步编程来解决这个问题,JavaScript执行异步任务时,不需要等待响应返回,可以继续执行其他任务,

而在响应返回时,会得到通知,并执行回调或者事件处理程序.

CPU不用等待而继续执行, 其实是交给,硬盘,光驱,声卡,网卡,显卡这些硬件, 他们是可以不消耗CPU资源而自动与内存交换数据的,这也是实现异步的基本条件,当数据交互完成,会告诉CPU执行完了,再触发指定的回调函数

就像打游戏要买一块好的显卡,因为涉及到大量的I/O操作应该就是为了对CPU能力的补充

当处理CPU走不开的异步操作就要通过线程池开启一个新的线程去完成,就变成了多线程

类似于排队的人有的人没带钱,需要找朋友来帮他付钱,需要时间比较长,就让后面的人先买,等他朋友来了再付钱,朋友就类似于硬盘和显卡.

简单描述下线程池:

线程池:基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。

单线程和多线程

说道这里稍微提一下单线程和多线程,

单线程就可以理解为你自己排队买吃的.

多线程就是你这边在排队买东西,你想买很多东西,但是排一次队只能买一个,

但是买东西的窗口有很多个,你就让你朋友去帮你排队买另一个这就形成了多线程

线程就可以理解为买东西排的队伍.

把你比作一个程序的话,通过朋友你就实现了同时占用两个线程

如果地方足够大的话,你还可以让你的家人帮你排别的队伍.

买东西的地方就可以理解为CPU.

线程补充介绍 : 点击链接


总结

  • 多线程
    • 最大的问题在于线程本身的调度和运行需要很多的时间,因此不建议创建太大量的线程;
    • 共享资源的调度比较难,涉及到死锁,上锁等相关的概念
  • 异步
    • 最大的问题在于回调,这增加了软件设计上的难度

在实际设计时,我们可以将两者结合起来:

当需要执行I/O操作时,使用异步操作比使用线程 + 同步I/O操作更合适

I/O操作不仅包括了直接的文件,网络的读写,还包括数据库操作,WebServer,HttpRequest以及.netRemoting等跨进程的调用

异步特别适用于大多数I/O密集型的应用程序

而线程的适用范围则是那种需要长时间CPU运算的场合,例如耗时较长的图形处理和算法执行

但是往往由于适用线程编程的简单和符合习惯,所以很多朋友往往会使用线程来执行耗时较长的I/O操作

这样在只有少数几个并发操作的时候还无伤大雅,如果需要处理大量的并发操作时就不合适了

并行和并发

而JavaScript其实是单线程的(只有一个队伍),但是又想实现多线程的效果,所以有了异步

并行

多线程可以实现边走路边打电话, 也就是同一时刻多任务同时进行 ,也就是并行

并发

而异步则是跑步跑不动了,但是我还没跑完,在我恢复体力之前去吃点东西,等恢复体力了再回来跑 , 虽然我不能同时吃饭和跑步,但是吃饭和跑步事件都发生了,也就是并发

并发模型

相关概念

异步编程实现并发执行的的基础是基于事件循环的并发模型

再讲并发模型之前先简单介绍下堆栈与队列

堆栈与队列

堆(heap)

随便堆砌数据,没有顺序,通常存储对象(引用类型)

栈(stack)

类似于口袋,先放进去的在最底下,想拿出来就要把后放进去的一个一个拿出来(先进后出),

通常存储函数参数和基本类型值变量(按值访问)

队列(queue)

先进先出的顺序存储数据结构

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

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

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

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

事件循环

得名于它通常的实现方式:

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

模型特点:

完整运行(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

从不阻塞

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

JS执行环境中的两个结构

消息队列(messagequeue)

消息队列也叫任务队列(taskqueue) : 存储待处理消息及对应的回调函数或事件处理程序.

执行栈(executioncontextstack)


   转载规则


《JavaScript同步和异步》 张磊 采用 知识共享署名 4.0 国际许可协议 进行许可。
  目录