众所周知,js是单线程的,说到线程,我们首先来仔细辨析一下线程和进程的知识。
一、进程与线程
cpu会给当前进程分配资源,进程是资源分配的最小单位,进程的资源会分配给线程使用,线程是CPU调度的最小单位
1 ——CPU就像是一个大型的工厂一样。它就像一座工厂,时刻在运行。
2 ——假定工厂的电力有限,一次只能供给一个车间使用。也就是说,一个车间开工的时候,其他车间都必须停工。背后的含义就是,单个CPU一次只能运行一个任务。
3 ——进程好比是一个工厂的车间,它代表CPU所能处理的单个任务,任意时刻,CPU只能运行一个车间的任务,也就是一个进程,其他进程处于等待状态。
4 —— 一个车间里有着许多的工人,他们协同完成一个任务。
5 ——单个工人就好比是一个线程,一个进程可以包括多个线程。
6 —— 车间里的空间是可以供工人们共享的,比如许多房间是每个工人都可以进出的。这象征一个进程的内存空间是共享的,每个线程都可以使用这些共享内存。
7 —— 可是,每间房间的大小不同,有些房间最多只能容纳一个人,比如厕所。里面有人的时候,其他人就不能进去了。这代表一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。
8 —— 一个防止他人进入的简单方法,就是门口加一把锁。先到的人锁上门,后到的人看到上锁,就在门口排队,等锁打开再进去。这就叫"互斥锁"
(Mutual exclusion,缩写 Mutex),防止多个线程同时读写某一块内存区域。
9 —— 还有些房间,可以同时容纳n个人,比如厨房。也就是说,如果人数大于n,多出来的人只能在外面等着。这好比某些内存区域,只能供给固定数目的线程使用。
10 —— 这时的解决方法,就是在门口挂n把钥匙。进去的人就取一把钥匙,出来时再把钥匙挂回原处。后到的人发现钥匙架空了,就知道必须在门口排队等着了。这种做法叫做"信号量"(Semaphore),用来保证多个线程不会互相冲突。
归纳一下:
- 以多进程的形式,允许多个任务同时进行。
- 以多线程的形式,允许单个任务分成不同的部分进行运行。
- 提供协调机制,一方面防止进程之间和线程之间产生冲突,另一方面允许进程之间和线程之间共享资源。
二、JavaScript是单线程
虽然说js是单线程的,但是浏览器并不是单线程的,浏览器中的许多异步行为都是由浏览器去新开一个线程去解决的,js引擎线程是浏览器的线程之一,由于js引擎线程本身是单线程的,所以我们平时说的js单线程指的就是这个了。
浏览器还包括很多其他线程,如界面渲染线程,浏览器事件触发线程,Http请求线程等。
1、证明js是单线程的
// 证明js是单线程的
function foo() {
console.log("first");
setTimeout(( function(){
console.log( 'second' );
}),5);
}
for (var i = 0; i < 1000000; i++) {
foo();
}
// 执行结果会首先全部输出first,然后全部输出second;尽管中间的执行会超过5ms。
2.js为什么要设计成单线程的,
JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。
HTML5中增加了web worker可以去创建一个子线程,但是这个线程仍旧是完全受主线程控制,因此,js的单线程性,依旧是没有变化的。
三、任务队列
单线程就意味着,所有的任务队列都需要排队,前一个任务结束,再执行后一个任务,如果前一个任务耗时很长,那么后一个任务就不得不排着。
如果排队是因为计算量大,CPU忙不过来,倒也算了,但是很多时候CPU是闲着的,因为IO设备(输入输出设备)很慢(比如Ajax操作从网络读取数据),不得不等着结果出来,再往下执行。
js语言的设计者也注意到了这个问题,这时候不管IO,挂起来,去执行等待中的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。
于是这样就出现了同步任务
与异步任务
了。
1、同步任务
同步任务是指主线程排队的任务,只有前一个执行完毕,后一个才能执行。
2、异步任务
不进入主线程,而是进入一个任务队列,只有任务队列,通知了主线程,某一个异步任务才会执行。
下面以AJAX请求为例,来看一下同步和异步的区别:
- 异步AJAX:
主线程:“你好,AJAX线程。请你帮我发个HTTP请求吧,我把请求地址和参数都给你了。”AJAX线程:“好的,主线程。我马上去发,但可能要花点儿时间呢,你可以先去忙别的。”
主线程::“谢谢,你拿到响应后告诉我一声啊。”
(接着,主线程做其他事情去了。一顿饭的时间后,它收到了响应到达的通知。)
- 同步AJAX:
主线程:“你好,AJAX线程。请你帮我发个HTTP请求吧,我把请求地址和参数都给你了。”AJAX线程:“......”
主线程::“喂,AJAX线程,你怎么不说话?”
AJAX线程:“......”
主线程::“喂!喂喂喂!”
AJAX线程:“......”
(一炷香的时间后)
主线程::“喂!求你说句话吧!”
AJAX线程:“主线程,不好意思,我在工作的时候不能说话。你的请求已经发完了,拿到响应数据了,给你。”
正是由于JavaScript是单线程的,而异步容易实现非阻塞,所以在JavaScript中对于耗时的操作或者时间不确定的操作,使用异步就成了必然的选择。异步是这篇文章关注的重点。
异步过程
- 所有的同步任务都再主线程上执行,形成一个
执行栈
。 - 主线程之外还存在一个
任务队列
,一旦任务队列中的异步任务执行完毕了,就会产生一个事件。 - 一旦主线程上的同步任务执行完毕了,那么系统就会读取
任务队列
,看看有哪些事件。那些事件对应的异步任务就结束等待状态,进入执行栈,开始执行。 - 主线程会不断的重复上诉过程。只要是主线程空了,那么就执行
任务队列
中的任务。
四、事件和回调函数
"任务队列"是一个事件的队列(也可以理解成消息的队列),IO设备完成一项任务,就在"任务队列"中添加一个事件,表示相关的异步任务可以进入"执行栈"了。主线程读取"任务队列",就是读取里面有哪些事件。
所谓的回调函数就是指被主线程挂起的代码,异步任务必须执行回调函数,当其产生事件,由主线程调入执行栈后就会执行这个回回调函数。
任务队列
就是一个先进先出
的一个数据结构,排在前面的事件,优先被主线程调用,主线程取得过程上是自动的,只有当主线程一变为空,那么任务队列的第一位就会进入主线程,那么就会执行对应的回调函数。
异步函数
A(args, callbackFn);
一个异步过程包括下面两个要素:
-
发起函数
(或叫注册函数)A
-
回调函数
`callbackFn`
它们都是在主线程上调用的,其中注册函数
用来发起异步过程
,回调函数
用来处理结果
来一个例子:
DOM点击事件
var button = document.getElement('#btn');
button.addEventListener('click', function(e) {
console.log(e);
});
从事件的角度来分析的话,在按钮上添加了一个鼠标点击的监听器,鼠标点击的时候出发。
从异步角度分析:
addEventListener
函数就是一个发起函数
,第二个回调参数就是回调函数, 事件触发的时候,表示异步任务执行完毕,就产生了事件,将其放入到消息队列
中去,等待主线成的调用。
这里又出现了一个新的词汇消息队列
,其实这里面放的就是任务队列执行完毕后的那些事件通知。等待着主线程的调用。接下来对其再仔细分析。
五、消息队列和事件循环(Event Loop)
未完待续~~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。