同步和异步
同步
所谓同步编程,就是计算机一行一行按顺序依次执行代码,是一种典型的请求-响应模型.
当前代码比较耗时会阻塞后续代码的执行,当请求调用一个函数或者方法后,需等待其响应返回,然后执行后续代码.
类似于生活中规范的排队,即使前面的人动作很慢,后面的人也要等着.
异步
一般情况下,同步编程,代码依次执行能很好的保证程序的执行,
但是在某些场景中,比如读取文件内容,或者请求服务器接口数据,这些请求需要根据返回的数据内容,来执行后续操作,
等待数据返回的时间中,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执行环境中的两个结构
消息队列也叫任务队列(taskqueue) : 存储待处理消息及对应的回调函数或事件处理程序.