一、浏览器四大进程

1.Browser进程:浏览器的主进程(负责协调、主控),只有一个。

    主要作用:

  • 负责浏览器界面显示,与用户交互。如前进,后退等
  • 负责各个页面的管理,创建和销毁其他进程
  • 将渲染(Renderer)进程得到的内存中的Bitmap(位图),绘制到用户界面上
  • 网络资源的管理,下载等

2、第三方插件进程:每种类型的插件对应一个进程,仅当使用该插件时才创建
3、GPU进程:最多一个,用于3D绘制等
4、浏览器渲染进程(即通常所说的浏览器内核)(Renderer进程,内部是多线程的):主要作用为页面渲染,脚本执行,事件处理等

 

二.浏览器多进程的优势

相比于单进程浏览器,多进程有如下优点:

  • 避免单个page crash影响整个浏览器
  • 避免第三方插件crash影响整个浏览器
  • 多进程充分利用多核优势
  • 方便使用沙盒模型隔离插件等进程,提高浏览器稳定性

简单点理解:如果浏览器是单进程,那么某个Tab页崩溃了,就影响了整个浏览器,体验有多差;同理如果插件崩溃了也会影响整个浏览器;而且多进程还有其它的诸多优势。当然,多进程,内存等资源消耗也会更大,有点空间换时间的意思。

三.渲染进程包括哪些线程

  1. GUI渲染线程
  • 负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。
  • 当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行
  • 注意,GUI渲染线程与JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起(相当于被冻结了),GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。
  1. JS引擎线程(单线程)
  • 也称为JS内核,负责处理Javascript脚本程序。(例如常常听到的谷歌浏览器的V8引擎,新版火狐的JaegerMonkey引擎等)
  • JS引擎线程负责解析Javascript脚本,运行代码。
  • JS引擎一直等待着任务队列中任务的到来,然后加以处理,一个Tab页(renderer进程)中无论什么时候都只有一个JS线程在运行JS程序
  • 同样注意,GUI渲染线程与JS引擎线程是互斥的,所以如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞。
  1. 事件触发线程
  • 归属于渲染进程而不是JS引擎,用来控制事件轮询(可以理解,JS引擎自己都忙不过来,需要浏览器另开线程协助)
  • 当JS引擎执行代码块如鼠标点击、AJAX异步请求等,会将对应任务添加到事件触发线程中
  • 当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理任务队列的队尾,等待JS引擎的处理
  • 注意,由于JS的单线程关系,所以这些待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行)
  • 定时触发器线程

    • 定时器setInterval与setTimeout所在线程
    • 浏览器定时计数器并不是由JavaScript引擎计数的,(因为JavaScript引擎是单线程的, 如果任务队列处于阻塞线程状态就会影响记计时的准确)
    • 因此通过单独线程来计时并触发定时(计时完毕后,添加到事件队列中,等待JS引擎空闲后执行)
    • 注意,W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms。
  • 异步http请求线程

  • 用于处理请求XMLHttpRequest,在连接后是通过浏览器新开一个线程请求。如ajax,是浏览器新开一个http线程
  • 将检测到状态变更(如ajax返回结果)时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入js引擎线程的事件队列中。再由JavaScript引擎执行。


    2084336019-5a65972413011.png

    知道了这几个线程,那么通过这几个线程,js是怎么执行的呢?

    三.渲染进程中的线程之间的关系

    GUI渲染线程与JS引擎线程互斥

    由于JavaScript是可操纵DOM的,如果在修改这些元素属性同时渲染界面(即JS线程和GUI线程同时运行),那么渲染线程前后获得的元素数据就可能不一致了。

    因此为了防止渲染出现不可预期的结果,浏览器设置GUI渲染线程与JS引擎为互斥的关系,当JS引擎执行时GUI线程会被挂起,
    GUI更新则会被保存在一个队列中等到JS引擎线程空闲时立即被执行。

    JS阻塞页面加载

    从上述的互斥关系,可以推导出,JS如果执行时间过长就会阻塞页面。

    譬如,假设JS引擎正在进行巨量的计算,所以JS引擎很可能很久很久后才能空闲,所以导致页面渲染加载阻塞。这就牵扯到script标签在html中的存放位置。具体可以看我另一篇文章 为什么script标签一般放在body下面

    四.js引擎是单线程的

    我们知道js是单线程的。也就是说,同一个时间只能做一件事。那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊。

    参考阮一峰大神的文章js事件轮询(Event Loop)

    • JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
    • 所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。
    • 为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

    五.js事件轮询

    上面我们已经知道JS引擎是单线程,任务应该是按顺序执行的,那么怎么会有同步异步之说?

    • 单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。
    • 如果排队是因为计算量大,CPU忙不过来,倒也算了,但是很多时候CPU是闲着的,因为IO设备(输入输出设备)很慢(比如Ajax操作从网络读取数据),不得不等着结果出来,再往下执行。
    • JavaScript语言的设计者意识到,这时主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。
    • 于是,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

    理解了同步异步。其实其最本质原因就是基于js的事件轮询机制。

    1. 所有同步任务都在主线程(即js引擎线程)上执行,形成一个执行栈
    2. 而异步任务均由事件触发线程控制,其有一个任务队列。只要异步任务有了运行结果,就在"任务队列"之中放置回调事件。异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。所以所谓"回调函数"(callback),就是那些会被主线程挂起来的代码。
    3. 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",按顺序结束等待状态,进入执行栈,开始执行。
    4. 主线程不断重复上面的第三步
    5. 只要主线程空了,就会去读取"任务队列",这个过程会不断重复。这就是JavaScript的运行机制。又称为Event Loop(事件循环或者轮询)。

    六.定时器触发线程

    上述事件循环机制的核心是:JS引擎线程和事件触发线程

    js来控制主线程,事件触发来控制任务队列就如主线程。

    为什么要单独的定时器线程?因为JavaScript引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确,因此很有必要单独开一个线程用来计时。

    什么时候会用到定时器线程?当使用setTimeout或setInterval时,它需要定时器线程计时,计时完成后就会将特定的事件推入事件触发线程的任务队列中。等待进入主线程执行。

    譬如:

    setTimeout(function(){
        console.log('hello!');
    }, 1000);
    

    这段代码的作用是当1000毫秒计时完毕后(由定时器线程计时),将回调函数推入事件队列中,等待主线程执行

    setTimeout(function(){
        console.log('hello!');
    }, 0);
    
    console.log('begin');
    
    //begin hello
    

    这段代码的效果是表示当前代码执行完(执行栈清空)以后,立即执行(0毫秒间隔)指定的回调函数。

    注意:

    • 虽然代码的本意是0毫秒后就推入事件队列,但是html5标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms。
    • 就算不等待4ms,就算假设0毫秒就推入事件队列,也会先执行begin(因为只有主线程可执行栈内空了后才会主动读取事件队列)。要是当前代码耗时很长,有可能要等很久,所以并没有办法保证,回调函数一定会在setTimeout()指定的时间执行。同理setInterval则是每次都精确的隔一段时间推入一个事件(但是,事件的实际执行时间不一定就准确,还有可能是这个事件还没执行完毕,下一个事件就来了)

    参考: 线程和进程
    https://segmentfault.com/a/1190000012925872


    posted on   ygunoil  阅读(3279)  评论(0编辑  收藏  举报
    编辑推荐:
    · AI与.NET技术实操系列:基于图像分类模型对图像进行分类
    · go语言实现终端里的倒计时
    · 如何编写易于单元测试的代码
    · 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
    · .NET Core 中如何实现缓存的预热?
    阅读排行:
    · 25岁的心里话
    · 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
    · 零经验选手,Compose 一天开发一款小游戏!
    · 通过 API 将Deepseek响应流式内容输出到前端
    · AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗
    < 2025年3月 >
    23 24 25 26 27 28 1
    2 3 4 5 6 7 8
    9 10 11 12 13 14 15
    16 17 18 19 20 21 22
    23 24 25 26 27 28 29
    30 31 1 2 3 4 5
    点击右上角即可分享
    微信分享提示