主要内容:
- 区分进程和线程
- 浏览器是多进程的(哪些进程,优势是什么,渲染进程的常驻线程,内核通信过程)
- 梳理浏览器内核中线程之间的关系
- JS运行机制
- 宏任务与微任务
学习内容:《 从浏览器多进程到JS单线程,JS运行机制最全面的一次梳理》By 撒网要见鱼
Learning Card
1. 区分进程和线程
进程 | 线程 | |
---|---|---|
有系统分配的内存(独立资源) | 有 | 无 |
进程/线程之间相互独立 | 是 | 否 |
进程
- 是cpu资源分配的最小单位(是能拥有资源和独立运行的最小单位)
- 进程之间相互独立
- 一个进程由一个或多个线程组成
线程
- 是cpu调度的最小单位(线程是建立在进程的基础上的一次程序运行单位)
- 多个线程在进程中协作完成任务
- 同一个进程下的各个线程之间共享程序的内存空间
我的理解:
其实可以用一个比较常用的软件来打比方。我们把CPU想象成我们自己,把程序都想象成EXCEL表格。一个进程就是一个excel文件,每个excel文件之间是相互独立的。打开一个excel文件,里面会有sheet,这个sheet就是线程。一个excel文件里可能有1个sheet,也可能有多个sheet,这就是单线程和多线程。
我们可以同时打开很多个excel文件,但是一次只能处理一个,这就是并发。也许有一天,人工智能发展到一定的阶段,可以虚拟出一个“我”,这时,好几个我可以同时处理不同的Excel文件,这就是并行。
2. 浏览器是多进程的
- 浏览器是多进程的
- 浏览器有一个主进程
- 每个tab页有一个独立的进程(浏览器的优化机制可能会合并进程,这一条并不绝对)
浏览器包含哪些进程
- Browser主进程(只有一个)
- 第三方插件进程(仅当使用时创建)
- GPU进程(最多一个,用于3D绘制)
- 浏览器渲染进程(默认每个tab一个)
浏览器多进程的优势
- 避免单个页面或插件crash影响整个浏览器
- 充分利用多核优势(避免资源浪费)
- 方便使用沙盒模型隔离插件等进程
浏览器内核(渲染进程)的常驻线程
- GUI渲染线程(渲染界面,重绘和回流)
- JS引擎线程(解析JS代码)
- 事件触发线程(控制事件循环,如AJAX等)
- 定时器触发线程(setTimeOut, setInterval,用来计时并触发定时)
- 异步HTTP请求线程(XMLHttpRequest连接)
注意点:
- GUI渲染线程与JS引擎线程是互斥的。JS引擎执行时GUI会被挂起,GUI更新会保存在一个队列中等JS引擎空闲时立即被执行;同样,JS执行时间过长会导致页面渲染不连贯
- 事件触发线程归属于浏览器而不是JS引擎
我的理解:
- GUI渲染线程负责解析HTML,CSS,形成界面
- JS引擎线程负责解析JS代码,如果是可以直接处理的代码,JS引擎会直接解析,如JS代码创建一个新的元素,添加在原有的div里
- JS引擎线程遇到无法立即处理的代码(异步操作,如setTimeOut, AJAX等),则放入事件处理线程,事件处理线程并不实际处理它们,只在事件符合触发条件时,将事件放入待处理队列,JS引擎空闲时会处理待处理队列
- 可以理解为JS引擎线程用于处理事件,事件触发线程用于安排事件如何排队
- 定时器触发用于计时和触发定时,等事件符合触发的时间时,将事件加入到待处理队列中,它同样是用来规定如何排队的,只是排队方式和事件处理线程不太一样(这也可以解释为什么setTimeOut并不是规定几秒之后执行,而是规定几秒之后加入到待处理队列中,如此时JS引擎线程并不空闲,那么时间并不会被马上处理,因此事件被处理的时间可能是长于我们预设的时间的)
- 异步http请求线程也是一样,它在XMLHttpRequest连接之后,检测到状态改变,将回调函数放入到事件队列中
- 可以看到事件处理线程、定时触发器线程、异步http请求线程本质上都是安排对应事件在符合触发条件之后,将反馈的,即需要处理的事件放入到待处理事件队列中,由JS引擎来执行
或许这样说更容易理解:
我们把这些线程理解成做菜的厨子。
- GUI渲染线程就是负责摆盘的,JS引擎线程是负责做菜的主厨,他们互相不能干预对方
- JS引擎线程遇到可以直接做的原料就自己动手把菜给炒了
- 有些原料不能马上做,可能需要等原料温度降到0度才可以动手,于是事件处理线程就等原料温度降到对应温度再交给主厨——JS引擎线程
- 有些原料必须腌制10分钟之后才能做,于是就该定时触发器线程负责,等到10分钟后把原料交给JS引擎线程
- 还有些原料必须打电话联系供应商送来才能做,于是就先给他们打电话确认可以送货(XMLHttpRequest),等到货送到了(状态改变),再烧高汤并把高汤交给主厨来做,这个对应的高汤就是异步http请求的回调函数
Browser进程和浏览器内核(Renderer进程)的通信过程
3. 梳理浏览器内核中线程之间的关系
GUI渲染线程与JS引擎线程互斥
- JS可以操作DOM,如果边操作DOM边渲染会出错
JS阻塞页面加载
- JS操作时,GUI渲染线程被挂起
- JS操作时间过长,看起来就像GUI渲染卡住了
WebWorker
- 创建一个Worker()对象来运行命名的JS文件,即申请开一个子线程
- workers在另一个全局上下文中,不同于当前的window
- JS还是单线程,但Worker线程里面不会影响JS引擎主线程
WebWorker与SharedWorkder
- WebWorker是tab页即render进程下的线程
- SharedWorker是浏览器的一个进程
我的理解:
- JS是单线程
- JS引擎和GUI渲染一次只能执行一个,另一个被挂起
- 这样会导致如果JS引擎执行时间太长,GUI看起来就卡住不动了
- 为了解决这个问题,提出了WebWorker这个解决方案
- WebWorker是一个子线程,专门用来计算运行JS文件
4. JS运行机制
- 同步任务都在主线程上执行
- 主线程运行时产生执行栈
- 事件触发线程管理着一个任务队列,异步任务有了运行结果,就在任务队列尾部放置事件
- 栈中任务执行完毕,回去读取事件队列中的事件
setInterval
的问题
- 累计效应:执行时间比预期小,可能导致连续运行没有间隔
5. macrotask和microtask
宏任务
每次执行栈执行的代码就是一个宏任务
完成后渲染,再执行下一个
微任务
宏任务执行结束后立即执行的任务
在渲染之前执行
类型 | |
---|---|
宏任务 | setTimeOut, setInterval |
微任务 | Promise, process.nextTick |