浏览器的多线程与JS的单线程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u014465934/article/details/87948882

0.前言

浏览器内核、渲染引擎和js引擎的关系:

一个完整的浏览器包含浏览器内核和浏览器的外壳(shell)。浏览器核心——内核分成两部分:渲染引擎和js引擎。由于js引擎越来越独立,内核就倾向于只指渲染引擎,所以浏览器内核现在几乎就可以相当于渲染引擎。

浏览器内核又可以分成两部分:渲染引擎(layout engineer或者RenderingEngine)和JS引擎。

渲染引擎功能作用

渲染引擎,负责对网页语法的解释(如HTML、JavaScript)并渲染网页。所以,通常所谓的浏览器内核也就是浏览器所采用的渲染引擎,渲染引擎决定了浏览器如何显示网页的内容以及页面的格式信息。不同的浏览器内核对网页编写语法的解释也有不同,因此同一网页在不同的内核的浏览器里的渲染(显示)效果也可能不同,这也是网页编写者需要在不同内核的浏览器中测试网页显示效果的原因。

当前主流渲染引擎内核:
firefox使用gecko引擎
IE使用Trident引擎
edge,使用edge引擎
opera最早使用Presto引擎,后来弃用
chrome\safari\opera使用webkit引擎
13年chrome和opera开始使用Blink引擎

JS引擎功能作用

最开始渲染引擎和js引擎并没有区分的很明确,后来JS引擎越来越独立,内核就倾向于只指渲染引擎。JavaScript最初由网景公司的Brendan Eich设计,是一种动态、弱类型、基于原型的语言,内置支持类。以它为基础,制定了ECMAScript标准。javascript在浏览器的实现中还必须含有DOM和BOM。Web浏览器一般使用公共 API来创建主机对象来负责将DOM对象反射进JavaScript。JS引擎负责对JavaScript进行解释、编译和执行,以使网页达到一些动态的效果。

参考文章:
https://blog.csdn.net/wangguoyu1996/article/details/81286319

1.浏览器内核-渲染引擎、js引擎

扫描二维码关注公众号,回复: 5627049 查看本文章

浏览器一般由七个模块组成,User Interface(用户界面)、Browser engine(浏览器引擎)、Rendering engine(渲染引擎)、Networking(网络)、JavaScript Interpreter(js解释器)、UI Backend(UI 后端)、Date Persistence(数据持久化存储) 如下图:

在这里插入图片描述

1.用户界面 -包括地址栏、后退/前进按钮、书签目录等,也就是你-所看到的除了页面显示窗口之外的其他部分
2.浏览器引擎 -可以在用户界面和渲染引擎之间传送指令或在客户端本地缓存中读写数据等,是浏览器中各个部分之间相互通信的核心
3.渲染引擎 -解析DOM文档和CSS规则并将内容排版到浏览器中显示有样式的界面,也有人称之为排版引擎,我们常说的浏览器内核主要指的就是渲染引擎
4.网络 -用来完成网络调用或资源下载的模块
5.UI 后端 -用来绘制基本的浏览器窗口内控件,如输入框、按钮、单选按钮等,根据浏览器不同绘制的视觉效果也不同,但功能都是一样的。
6.JS解释器 -用来解释执行JS脚本的模块,如 V8 引擎、JavaScriptCore
7.数据存储 -浏览器在硬盘中保存 cookie、localStorage等各种数据,可通过浏览器引擎提供的API进行调用

渲染引擎:
在维基百科上是这样介绍浏览器内核的,网页浏览器的排版引擎(Layout Engine或Rendering Engine)也被称为浏览器内核、页面渲染引擎、解释引擎或模板引擎,它负责取得网页的内容(HTML、XML、图像等等)、整理消息(例如加入CSS等),以及计算网页的显示方式,然后会输出至显示器或打印机。所有网页浏览器、电子邮件客户端以及其它需要根据表示性的标记语言(Presentational markup)来显示内容的应用程序都需要排版引擎

JavaScript引擎:
JS引擎负责解析Javascript语言,执行javascript语言来实现网页的动态效果。

主流浏览器渲染引擎和JS引擎:
在这里插入图片描述
主流浏览器内核:
1、IE浏览器内核:Trident内核,也是俗称的IE内核;
2、Chrome浏览器内核:统称为Chromium内核或Chrome内核,以前是Webkit内核,现在是Blink内核;
3、Firefox浏览器内核:Gecko内核,俗称Firefox内核;
4、Safari浏览器内核:Webkit内核;
5、Opera浏览器内核:最初是自己的Presto内核,后来是Webkit,现在是Blink内核;
6、360浏览器、猎豹浏览器内核:IE+Chrome双内核;
7、搜狗、遨游、QQ浏览器内核:Trident(兼容模式)+Webkit(高速模式);
8、百度浏览器、世界之窗内核:IE内核;
9、2345浏览器内核:以前是IE内核,现在也是IE+Chrome双内核;

参考文章:https://blog.csdn.net/BonJean/article/details/78453547

1.浏览器的进程与线程

参考文章可以看:
https://blog.csdn.net/it_rod/article/details/79880745

首先打开浏览器,然后打开shift + Esc打开chrome的任务管理器

在这里插入图片描述
此时只有三个进程:
1.浏览器进程(Browser进程):
浏览器的主进程(负责协调、主控),只有一个。作用有负责浏览器界面显示,与用户交互。如前进,后退等负责各个页面的管理,创建和销毁其他进程。将Renderer进程得到的内存中的Bitmap,绘制到用户界面上,网络资源的管理,下载等。

2.GPU进程:
用于3D绘制等(可禁止掉,而且这个与页面渲染过程的Composite Layers 有关系,后面性能优化相关文章学习到再来研究一下GPU)。

3.浏览器渲染进程(Renderer进程,内部是多线程的):
每一个标签页的打开都会创建一个浏览器渲染进程(浏览器内核)。默认每个Tab页面一个进程,互不影响。主要作用为页面渲染,脚本执行,事件处理等。

2.浏览器为什么要多进程

把所有网页都放进一个进程的浏览器面临在健壮性,响应速度,安全性方面的挑战。因为如果浏览器中的一个tab网页崩溃的话,将会导致其他被打开的网页应用。另外相对于线程,进程之间是不共享资源和地址空间的,所以不会存在太多的安全问题,而由于多个线程共享着相同的地址空间和资源,所以会存在线程之间有可能会恶意修改或者获取非授权数据等复杂的安全问题。

3.Browser进程与Render进程,GPU进程之间如何合作?

在How browser work为文章中看到过这样一幅图:

在这里插入图片描述

这里的Browser engine我想对应的就是Browser进程,Rendering engine对应的就是Render进程。
针对与用户打开一个标签页,可以看到首先控制的还是Browser进程。然后我们再看一下chromium多线程模型:

在这里插入图片描述

基本工作方式如下:

Browser进程收到用户的请求,首先由UI线程处理,而且将相应的任务转给IO线程,他随机将该任务传递给Render进程;
Render进程的IO线程经过简单解释后交给渲染线程,渲染线程接收请求,加载网页并渲染网页,这其中可能需要Browser进程获取资源和需要GPU进程来帮助渲染,最后Render进程将结果由IO线程传递给Browser进程;
Browser进程接收到结果并将结果绘制出来;

4.浏览器渲染Render进程(浏览器内核)有哪些线程

1.GUI渲染线程

负责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。
当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。
注意,GUI渲染线程与JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起(相当于被冻结了),GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。

2.JS引擎线程

也称为JS内核,负责处理Javascript脚本程序。(例如V8引擎)
JS引擎线程负责解析Javascript脚本,运行代码。
JS引擎一直等待着任务队列中任务的到来,然后加以处理,一个Tab页(renderer进程)中无论什么时候都只有一个JS线程在运行JS程序
同样注意,GUI渲染线程与JS引擎线程是互斥的,所以如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞。

3.事件触发线程

归属于浏览器而不是JS引擎,用来控制事件循环(可以理解,JS引擎自己都忙不过来,需要浏览器另开线程协助)
当JS引擎执行代码块如setTimeOut时(也可来自浏览器内核的其他线程,如鼠标点击、AJAX异步请求等),会将对应任务添加到事件线程中
当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理
注意,由于JS的单线程关系,所以这些待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行)

4.定时触发器线程

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

5.异步HTTP请求线程

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

5.JS引擎线程的相关介绍

(1) 为什么JavaScript是单线程?
首先在明确一个概念,JS引擎线程生存在Render进程(浏览器渲染进程)。其实从前面的进程,线程之间的介绍已经明白,线程之间资源共享,相互影响。假设javascript的运行存在两个线程,彼此操作了同一个资源,这样会造成同步问题,修改到底以谁为标准。
所以,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。
(2) WebWorker会造成js多线程吗?

首先举一个例子,可以查看web worker:

//index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>webWorker</title>
</head>
<body>
  <div>
    <a href='./webworker/index.html'>web worker</a>
  </div>
</body>
</html>

//worker.js
onmessage = function (count) {
  console.log("web worker start")
 
    var i = 0;
 
    while(i < count.data) {
      i ++;
    }
 
    postMessage("web worker finish");
};

然后打开performence面板查看:

在这里插入图片描述

图中蓝色框是浏览器下载完wokder.js文件。紧接着我们我们可以看到红色框DedicatedWorker Thread,在workder thread的时间内去执行worker.js,然后将计算好的结果返回给main thread,最后执行到蓝色框中去。

这样看起来好像是创建了一个新的线程。如果我没有使用web worker的情况下,DedicatedWorker Thread根本就不存在。
这好像看起来并没有什么说服性。我们再看一个例子,在本地查看这个例子的时候,具体的数字是我调的,是想达到下面图示的效果。

//index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>webWorker</title>
</head>
<body>
    <script>
        var worker = new Worker("worker.js");
        worker.postMessage(123456);
 
 
        worker.onmessage = function (e) {
            console.log(e.data)
        };
 
        setTimeout(function() {
            var i = 0;
 
            while(i < 9234156) {
                i ++;
            }
        }, 180);
    </script>
</body>
</html>

//worker.js
onmessage = function (count) {
  console.log("web worker start")
 
    var i = 0;
 
    while(i < count.data) {
      i ++;
    }
 
    postMessage("web worker finish");
};

如图所示,setTimeout的代码执行的时候,worker.js的代码也在执行。这里我得到的结果是webworker可以在js引擎执行代码的时候去执行另外的代码。

在这里插入图片描述

这里我们需要在明确开始的问题了,是造成js执行的多线程还是js引擎的多线程。这里是两个概念。上面的图示中main的栏目中是浏览器渲染Render进程(本人猜测),因为在这个过程中我们可以看到js代码的执行,也有GUI渲染线程进行html代码的解析。现在我看到的是DedicatedWorker Thread是与览器渲染Render进程同一级的(当然这些都是performence展现出来给我们看的)。

我在MDN上看到一句话:Worker接口会生成真正的操作系统级别的线程。所以这里的webworker不是一个新的js引擎线程。而是操作系统级别的线程。线程的执行不会影响到原有的js引擎的执行,也不会影响到浏览器渲染Render进程。至于其内部实现,本人就望尘莫及了。但是,人家webworker确实实现了js代码执行的多线程(当然这些都是本人的基于看到的结果猜测的,没有找到实际的论证资料,如果有知道的可以告知,谢谢了)。

所以我目前得到的结论是: webworker是可以造成js代码的多线程执行,但不是js引擎多线程的执行。webwoker的生命周期是由js引擎线程控制的,因为webweoker提供了一系列的api供我们操作。

然后我们再说一下webweoker中的一些不能操作的内容:也是出于安全考虑,如果不太小心,那么并发(concurrency)会对你的代码产生有趣的影响。然而,对于 web worker 来说,与其他线程的通信点会被很小心的控制,这意味着你很难引起并发问题。所以webworker也自己做了限制(下面的内容是在网上找到的,因为我没有这么使用过webworker):
1、不能访问DOM和BOM对象的,Location和navigator的只读访问,并且navigator封装成了WorkerNavigator对象,更改部分属性。无法读取本地文件系统
2、子线程和父级线程的通讯是通过值拷贝,子线程对通信内容的修改,不会影响到主线程。在通讯过程中值过大也会影响到性能(解决这个问题可以用transferable objects)
3、并非真的多线程,多线程是因为浏览器的功能
4、兼容性
5 因为线程是通过importScripts引入外部的js,并且直接执行,其实是不安全的,很容易被外部注入一些恶意代码
6、条数限制,大多浏览器能创建webworker线程的条数是有限制的,虽然可以手动去拓展,但是如果不设置的话,基本上都在20条以内,每条线程大概5M左右,需要手动关掉一些不用的线程才能够创建新的线程(相关解决方案)
7、js存在真的线程的东西,比如SharedArrayBuffer

(3)js代码的执行(Event Loop)与其他线程之间的合作

JavaScript 引擎并不是独立运行的,它运行在宿主环境中,对多数开发者来说通常就是Web 浏览器。提供了一种机制来处理程序中多个块(这里的块可以理解成多个回掉函数)的执行,且执行每块时调用JavaScript 引擎,这种机制被称为事件循环。换句话说,JavaScript 引擎本身并没有时间的概念,只是一个按需执行JavaScript 任意代码片段的环境。“事件”(JavaScript 代码执行)调度总是由包含它的环境进行。这个调度是由事件触发线程调度的。

举例来说,如果你的JavaScript 程序发出一个Ajax 请求,从服务器获取一些数据,那你就在一个函数(通常称为回调函数)中设置好响应代码,然后JavaScript 引擎会通知宿主环境(事件触发线程):“嘿,现在我要暂停执行,你一旦完成网络请求,拿到了数据,就请调用这个函数。”然后浏览器就会设置侦听来自网络的响应,拿到要给你的数据之后,就会把回调函数插入到事件循环,以此实现对这个回调的调度执行。

请看下图对于一个页面的请求以及js的执行过程中,上面的进程/线程之间的合作。

由于图片比较大,详情可以访问:
https://www.processon.com/view/link/593fc61fe4b0848d3ecf1d56)。

6.Promise的出现

其实关于Promise的内容我之前也有看过,具体内容可以查看文章现在这里我只是将Promise基于任务队列的内容用代码的形式展示出来。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>webWorker</title>
</head>
<body>
    <script>
      setTimeout(function() {
        console.log('setTimeout run');
      }, 0);
 
        new Promise(function(resolve, reject) {
        resolve();
        })
        .then(function(){  
        console.log('promise run');
    } );  
    </script>
</body>
</html>

这里的输出结果相信大家猜测的出来:
promise run
setTimeout run

这里的原因不在多说,因为首先js引擎要先执行主线程js的代码(会先执行完,因为一个js的加载,从上往下会执行完成之后,js引擎才会有时间去从事件循环队列中拿出代码块执行),至于setTimeout,间隔时间尽管为0ms,其实真正执行的时候是4ms。而且回掉函数是放在事件循环队列里的。那么Promise呢?好比我们现在执行的是js主线程,执行完成之后,js引擎不会立即去事件循环队列里取代码块执行,而是说当前主线程还有一点事情没有做完,那就是promise,在之前的文章中也谈过。二者事件的粒度不同,promsie是事件循环队列之上的任务队列。


我觉得下面这篇文章也不错,可以参考:
https://blog.csdn.net/github_34514750/article/details/76577663

0.前言

开发过程中遇到js线程和ui渲染线程互斥问题。导致ui无法正常更新等问题。这些问题的根源就是因为浏览器的多线程和js的单线程引起的。

看本篇博客之前,应该充分理解消息队列,事件循环,同步异步任务等概念。

这些概念以前都知道,也了解多线程的概念。但是当遇到问题的时候,这些东西都被抛到脑后,值得深思。

1.JS单线程

js运作在浏览器中,是单线程的,js代码始终在一个线程上执行,此线程被称为js引擎线程。

web worker也只是允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。

但是如果单线程,任务都需要排队。排队是因为计算量大,CPU忙不过来,倒也算了,但是很多时候CPU是闲着的,因为IO设备(输入输出设备)很慢(比如Ajax操作从网络读取数据),不得不等着结果出来,再往下执行。

JavaScript语言的设计者意识到,这时主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。

于是,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。

2.浏览器多线程

1.js引擎线程(js引擎有多个线程,一个主线程,其它的后台配合主线程)
作用:执行js任务(执行js代码,用户输入,网络请求)

2.ui渲染线程
作用:渲染页面(js可以操作dom,影响渲染,所以js引擎线程和UI线程是互斥的。js执行时会阻塞页面的渲染。)

3.浏览器事件触发线程
作用:控制交互,响应用户

4.http请求线程
作用:ajax请求等

5.定时触发器线程
作用:setTimeout和setInteval

6.事件轮询处理线程
作用:轮询消息队列,event loop

所以异步是浏览器的两个或者两个以上线程共同完成的。比如ajax异步请求和setTimeout。

3.同步任务和异步任务

同步任务:在主线程排队支持的任务,前一个任务执行完毕后,执行后一个任务,形成一个执行栈,线程执行时在内存形成的空间为栈,进程形成堆结构,这是内存的结构。执行栈可以实现函数的层层调用。注意不要理解成同步代码进入栈中,按栈的出栈顺序来执行。

异步任务会被主线程挂起,不会进入主线程,而是进入消息队列,而且必须指定回调函数,只有消息队列通知主线程,并且执行栈为空时,该消息对应的任务才会进入执行栈获得执行的机会。

主线程执行的说明: 【js的运行机制】
(1)所有同步任务都在主线程上执行,形成一个执行栈。
(2)主线程之外,还存在一个”任务队列”。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件。
(3)一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。

在这里插入图片描述

4.事件循环

主线程从”任务队列”中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。

在这里插入图片描述
如上图,我们可以看出来同步运行执行栈时会调用各种WebAPIs,这样就导致各种事件到了任务队列中,同步执行完成之后,就可以异步执行任务队列中,任务队列中到执行栈中依然是同步执行。

上图中,主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各种外部API,它们在”任务队列”中加入各种事件(click,load,done)。只要栈中的代码执行完毕,主线程就会去读取”任务队列”,依次执行那些事件所对应的回调函数。

所以回调函数不一定是异步

执行栈中的代码(同步任务),总是在读取”任务队列”(异步任务)之前执行。

5.JS引擎线程(ajax)和UI线程互斥

这个没太看懂,可以直接去原作者文章参考:
https://blog.csdn.net/github_34514750/article/details/76577663


这两篇文章也非常不错:
https://www.cnblogs.com/yasmi/articles/5064588.html
https://juejin.im/post/5a6547d0f265da3e283a1df7

猜你喜欢

转载自blog.csdn.net/u014465934/article/details/87948882