js Event Loop学习笔记。
1、线程和进程的区别
一个程序至少有一个进程,一个进程至少有一个线程。线程的划分尺度小于进程,使得多线程程序的并发性高。
另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大的提高了程序的运行效率。
线程在执行过程中与进程还是有区别的,每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
从逻辑角度来看,多线程意义在于一个应用程序中,有多个执行部分可以同时执行。但是操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理及资源分配。
2、js为什么是单线程的
JavaScript语言的一大特点就是单线程,也就是说同一时间只能做一件事。这个特性与它的用途有关,作为浏览器脚本语言,js的主要用途是与用户互动以及操作DOM。这决定了它只能是单线程的,否则会带来很多复杂的同步问题。比如,假定js同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除这个节点,这时浏览器应该以哪个线程为准呢?
3、单线程的优劣势
优势:**
1、降低处理复杂性,简化开发。
2、作为用于预处理与用户互动的脚本语言,可以更加容易的处理状态同步的问题。
劣势:
无并发处理能力,任务处于I/O等待状态,导致CPU处理资源的浪费。
于是JavaScript语言将任务分为:同步任务和异步任务。
4、同步
如果在函数返回的时候,调用者就能够得到预期的结果(即拿到预期的返回值或者看到了预期的结果),那么这个函数就是同步的。
Math.sqrt(4);
console.log('Hi');
第一个函数返回时,就拿到了预期的返回值:4的平方根。第二个函数返回时,就看到了预期的效果:在控制台打印字符串Hi。所以这两个函数是同步的。
5、异步
如果在函数返回的时候,调用者还不能够拿到预期的结果,而是需要在将来通过一定的手段得到,那么这个函数就是异步的。
fs.readFile('test.txt', 'utf8', function(err, data) {
console.log(data);
});
在上面的代码中,我们希望通过fs.readFile函数读取文件test.txt中的内容,并打印出来。但是在fs.readFile函数返回时,我们期望的结果并不会发生,而是要等到文件全部读取完成之后。如果文件很大的话可能要很长时间,所以,fs.readFile函数是异步的。
正是由于JavaScript是单线程的,而异步容易实现非阻塞,所以在JavaScript中对于耗时的操作或者时间不确定的操作,使用异步就成了必然的选择。
从上文可以看出,异步函数实际上很快就调用完成了。但是后面还有执行异步操作、通知主线程、主线程调用回调函数等很多步骤。我们把整个过程叫做异步过程。异步函数的调用在整个异步过程中,只是一小部分
一个异步过程通常是这样的:主线程发起一个异步请求,异步任务接收请求并告知主线程已收到(异步函数返回);主线程可以继续执行后面的代码,同时异步操作开始执行;执行完成后通知主线程;主线程收到通知后,执行一定的动作(调用回调函数)
因此,一个异步过程包括两个要素:注册函数和回调函数,其中注册函数用来发起异步过程,回调函数用来处理结果。
6、任务队列
对于同步任务来说,按顺序执行即可;但是,对于异步任务,各任务执行的时间长短不同,执行完成的时间点也不同,主线程如何调控异步任务呢?这就用到了消息队列
有些文章把消息队列称为任务队列,或者叫事件队列,总之是和异步任务相关的队列
可以确定的是,它是队列这种先入先出的数据结构,和排队是类似的,哪个异步操作完成的早,就排在前面。不论异步操作何时开始执行,只要异步操作执行完成,就可以到消息队列中排队
这样**,主线程在空闲的时候,就可以从消息队列中获取消息并执行**
消息队列中放的消息具体是什么东西?消息的具体结构当然跟具体的实现有关。但是为了简单起见,可以认为:消息就是注册异步任务时添加的回调函数。
JS是有两个任务队列的,一个叫做Macrotask Queue(Task Queue),一个叫做Microtask Queue;
**Macrotask Queue(宏任务):**进行比较大型的工作,常见的有setTimeout,setInterval,用户交互操作,UI渲染等;
**Microtask Queue(微任务):**进行较小的工作,常见的有Promise、new MutaionObserver()、Process.nextTick;
任务队列中的微任务先执行,然后执行宏任务。
7、事件循环 Event Loop
JavaScript调控同步和异步的机制叫做事件循环。
事件循环大致分为以下几个步骤:
(1)所有同步任务都在主线程上执行,形成一个执行栈。
(2)主线程之外,还存在一个"任务队列"。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。
例子,看下面程序的输出结果:
console.log('start');//同步任务
const interval = setInterval(() => {//异步任务,进入任务队列,宏任务
console.log('setInterval');
}, 0);
setTimeout(() => {/异步任务,进入任务队列,宏任务
console.log('setTimeout 1');
Promise.resolve()
.then(() => {
console.log('promise1');
}).then(() => {
setTimeout(() => {
console.log('setTimeout 2');
clearInterval(interval);
}, 0);
})
}, 0);
Promise.resolve()
.then(() => {/异步任务,进入任务队列,微任务
console.log('promise2');
});
结果: