阅读 189

线程,进程,EventLoop(事件循环)

线程与进程

我们经常说 JS 是单线程执行的,指的是一个进程里只有一个主线程。

那到底什么是线程?什么是进程?

官方的说法是:进程是 CPU 资源分配的最小单位;线程是 CPU 调度的最小单位。

  • 进程好比工厂,有单独专属于自己的工厂资源。

  • 线程好比工人,多个工人在一个工厂协作。一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线。

  • 工人们共享工厂的空间。进程的内存空间是共享的,每个线程都可以用这些共享内存。

  • 多个工厂之间是独立存在的。

多进程与多线程

多进程

在同一个时间里,同一个计算机系统中如果允许两个或两个以上的进程处于运行状态。多进程带来的好处是明显的。比如你可以听歌词的同事打开编辑器敲代码,编辑器和听歌软件的进程之间丝毫不会相互干扰。

多线程

程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务。允许单个程序创建多个并行执行的线程来完成各自的任务。以 chrome 为例,当你打开一个 tab 页面时,其实就是创建了一个进程,一个进程中可以有多个线程,比如渲染线程,js 引擎线程, http 请求线程等等。当你发起一个请求时,其实就是创建了一个线程,当请求结束后,该线程可能就会被销毁。

浏览器内核

浏览器内核是多线程,在内核控制下各线程相互配合保持同步,一个浏览器通常由一下常驻线程组成:

  • GUI 渲染线程

  • JavaScript 引擎线程

  • 定时触发器线程

  • 事件触发线程

  • 异步 http 请求线程

GUI 渲染线程

  • 主要负责页面的渲染,解析 HTMLCSS,构建 DOM 树, 布局和绘制等。

  • 当界面需要重绘或者由于某种操作引发回流时,将执行该线程。

  • 将线程与 JS 引擎线程互斥,当执行 JS 引擎线程时,GUI 渲染会被挂起,当任务队列闲空时,主线程才会去执行 GUI 渲染。

JavaScript 引擎线程

  • 该线程当然是主要负责处理 JavaScript 脚本,执行代码。

  • 也是主要负责执行准备好待执行的事件,即定时器计数结束,或者异步请求成功并正确返回时,将依次进入任务队列,等待 JS 引擎线程的执行。

  • 当然,该线程与 GUI 渲染线程互斥,当 JS 引擎线程执行 JavaScript 脚本时间过长,将导致页面渲染的阻塞。

定时触发器线程

  • 负责执行异步定时器一类的函数的线程,如:setTimeout, setInterval

  • 主线程依次执行代码时,遇到定时器,会将定时器交给该线程处理,当计数完毕后,事件触发线程会将计数完毕后的事件加入到任务队列的尾部,等待 JS 引擎线程执行。

事件触发进程

  • 主要负责将准备好的事件交给 JS 引擎线程执行。 比如 setTimeout 定时器计数结束, ajax 等异步请求成功并触发回到函数,或者用户触发点击事件时,该线程会将整装待发的时候依次加入到任务队列的队尾,等待 JS 引擎线程的执行。

异步 http 请求线程

  • 负责执行异步请求一类的函数的线程,如: Promise, axios, ajax等。

  • 主线程依次执行代码时,遇到异步请求,会将函数交给该线程处理,当监听到状态码变更,如果有回调函数,事件触发线程会将回调函数加入到任务队列的尾部,等待 JS 引擎线程执行。

浏览器中的 Event Loop(事件循环)

Micro-Task 与 Macro-Task

浏览器端事件循环中的异步队列有两种:macro(宏任务)队列和 micro(微任务)队列。宏任务队列可以有多个,微任务队列只有一个。

  • 常见的 macro-task 比如:setTimeoutsetIntervalscript (整体代码)、I/O 操作、UI 渲染等。

  • 常见的 micro-task 比如: new Promise().then(回调)MutationObserver (html5 新特性)等。

Event Loop 过程解析

Event Loop 过程解析

  • 一开始执行栈空,我们可以把执行栈认为是一个存储函数调用的栈结构,遵循先进后出的原则。micro 队列空, macro 队列里有且只有一个 script 脚本(整体代码)。

  • 全局上下文(script 标签)被推入执行栈,同步代码执行。在执行的过程中,会判断是同步任务还是异步任务,通过对一些接口的调用,可以产生新的 macro-task 与 micro-task,它们会分别被推入到各自的任务队列里。同步代码执行完了,script 脚本会被移除出 macro 队列,这个过程本质上是队列的 macro-task 的执行和出队的过程。

  • 上一步我们出队的是一个 macro-task,这一步我们处理的是 micro-task。但需要注意的是:当 macro-task 出队时, 任务是一个一个执行的;而 micro-task 出队时,任务是一队一队执行的。因此。我们处理 micro 队列这一步,会逐个执行队列中的任务并把它出队,直到队列被清空。

  • 执行渲染操作,更新界面。

  • 检查是否存在 web workder 任务,如果有,则对其进行处理。

  • 上述过程循环往复,直到两个队列都清空。

我们总结一下,每一次循环都是一个这样的过程:

循环过程

当某个宏任务执行完后,会查看是否有微任务队列。如果有,先执行微任务队列中的所有任务,如果没有,会读取宏任务队列中排在最前的任务,执行宏任务的过程中,遇到微任务,依次加入微任务队列。栈空后,再次读取微任务队列里的任务,以此类推。

  Promise.resolve().then(() => {     console.log('Propmise1')     setTimeout(() => {       console.log('setTimeout2')     }, 0)   })   setTimeout(() => {     console.log('setTimeout1')     Promise.resolve().then(() => {       console.log('Promise2')     })   }, 0) 复制代码

最后输出结果是 Promise1, setTimeout1, Promise2, setTimeout2

  • 一开始执行栈的同步任务(这属于宏任务)执行完毕,会去查看是否有微任务队列,上题中存在(有且只有一个),然后执行微任务队列中的所有任务输出 Promise1,同时会生成一个宏任务 setTimeout2

  • 然后去查看宏任务队列,宏任务 setTimeout1 在  setTimeout2 之前,先执行宏任务 setTimeout1,输出 setTimeout1

  • 在执行宏任务setTimeout1时会生成微任务 Promise2,加入微任务队列中,接着先去清空微任务队列中的所有任务,输出 Promisee2

  • 清空完微任务队列中的所有任务后,就又会去宏任务队列取一个,这回执行的是 setTimeout2


作者:前端一休
链接:https://juejin.cn/post/7039155976548663303

文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐