浏览器线程机制与事件机制

线程与进程

进程

  • 程序的一次执行, 它占有一片独有的内存空间
  • 可以通过 windows 任务管理器查看进程

线程

  • 是进程内的一个独立执行单元
  • 是程序执行的一个完整流程
  • 是CPU的最小的调度单元

关系

  • 程序是在某个进程中的某个线程执行的
  • 一个进程中至少有一个运行的线程:主线程, 进程启动后自动创建
  • 一个进程中也可以同时运行多个线程, 我们会说程序是多线程运行的
  • 一个进程内的数据可以供其中的多个线程直接共享
  • 多个进程之间的数据是不能直接共享的
  • 线程池( thread pool ):保存多个线程对象的容器, 实现线程对象的反复利用

多进程与多线程

多进程:一应用程序可以同时启动多个实例运行。
多线程:在一个进程内, 同时有多个线程运行。

比较单线程与多线程

比较 单线程 多线程
优点 顺序编程简单易懂 能有效提升CPU的利用率
缺点 效率低 创建多线程开销
线程间切换开销
死锁与状态同步问题

浏览器运行是单进程还是多进程?

  • 有的是单进程
    • firefox
    • 老版IE
  • 有的是多进程
    • chrome
    • 新版IE

如何查看浏览器是否是多进程运行的呢? == 任务管理器==>进程

浏览器运行是单线程还是多线程?

浏览器都是多线程运行的

浏览器内核

浏览器内核(browser core)是支持浏览器运行的最核心的程序。

各个浏览器的内核

浏览器 内核
Chrome、Safari webkit
firefox Gecko
IE Trident
360、搜狗等国内浏览器 Trident + webkit (双内核)

浏览器内核模块组成

  • 主线程
    • js 引擎模块 :负责 js 程序的编译与运行
    • html,css 文档解析模块 :负责页面文本的解析
    • DOM/CSS 模块 :负责 dom/css 在内存中的相关处理
    • 布局和渲染模块 :负责页面的布局和效果的绘制(内存中的对象)
  • 分线程
    • 定时器模块 :负责定时器的管理
    • 事件响应模块 :负责事件的管理
    • 网络请求模块 :负责 Ajax 请求

js线程

JavaScript 的执行是单线程的。所有的 JavaScript 代码,包括回调代码,最终都会在执行栈(execution stack)中执行。

JavaScript 的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript 的主要用途是与用户互动,以及操作 DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。

浏览器的事件循环模型

浏览器将代码分为两类:

  • 初始化执行代码:普通代码,包含绑定dom事件监听, 设置定时器, 发送ajax请求的代码
  • 回调执行代码:处理回调逻辑(回调函数)

模型的2个重要组成部分

  • 事件管理模块(下图的 Web APIs部分)
  • 回调队列(callback queue)

image

事件循环模型的运转流程

  • 执行执行栈中的初始化代码,将事件回调函数交给对应模块管理。
  • 当事件发生时, 管理模块会将回调函数及其数据添加到回调列队(callback queue)中。
  • 只有当执行栈中的初始化代码执行完后(可能要一定时间), 才会遍历读取回调队列中的回调函数并执行。

定时器问题

通过上面介绍过的事件循环运转流程,可以得知,定时器(setTimeout)回调函数只有在执行栈中的初始化代码全部执行完后才执行。因此,定时器并不能保证真正定时执行,如果在主线程在启动定时器之后执行了一个长时间的操作(时间超过定时器设置的时间),就会导致定时器回调函数延时处理。

Web Workers

传统 JavaScript 是单线程运行的,HTML5 新推出了一个 Web Worker 接口,可以实现在分线程中执行一个单独的 js 文件。

Web Workers是一种机制,通过它可以使一个脚本操作在与Web应用程序的主执行线程分离的后台线程中运行。这样做的优点是可以在单独的线程中执行繁琐的处理,让主(通常是UI)线程运行而不被阻塞/减慢。

Web Workers API

一个 worker 是使用构造函数创建的一个对象(例如,Worker()), 运行一个命名的 JavaScript文件 — 这个文件包含了将在 worker 线程中运行的代码,并且 worker 在与当前 window 不同的另一个全局上下文中运行。这个上下文由专用worker的情况下的一个 DedicatedWorkerGlobalScope 对象表示(标准 workers 由单个脚本使用; 共享 workers 使用 SharedWorkerGlobalScope )。

在 worker 线程中可以运行任意的代码,以下情况除外:不能直接在 worker 线程中操纵 DOM 元素, 或者使用某些 window 对象中默认的方法和属性。 但是 window 对象中很多的方法和属性是可以使用的,包括 WebSockets,以及诸如 IndexedDB 和 FireFox OS 中独有的 Data Store API 这一类数据存储机制。

主线程和 worker 线程之间通过这样的方式互相传输信息:两端都使用 postMessage() 方法来发送信息, 并且通过 onmessage 这个 event handler 来接收信息。 (传递的信息包含在 Message 这个事件的数据属性内) 。数据的交互是通过传递副本,而不是直接共享数据。

一个 worker 可以生成另外的新的 worker,这些 worker 的宿主和它们父页面的宿主相同。 此外,worker 可以通过 XMLHttpRequest 来访问网络,只是 XMLHttpRequest 的 responseXML 和 channel 这两个属性将总是 null 。

Web Workers 使用实例

实现效果

在输入框中输入一个数字 n,点击按钮,得到斐波那契数列中第 n 个数字的值。

分析

当数字 n 的值较大时,计算结果耗用时间比较长,如果计算的过程在主线程执行,则这段时间内页面将无法操作。

这种情况下,可以将计算的过程放在一个分线程中执行,主线程则可以继续执行其他代码,不会导致页面无法操作。当分线程得到结果之后,再将数据返回给主线程,主线程接收到数据,再进行处理。

代码

页面(主线程)代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<body>

<input type="text" id="number" value="30">
<button id="btn2">分线程计算fibonacci值</button>

<script type="text/javascript">

let number = document.getElementById('number');
let btn2 = document.getElementById('btn2');

btn2.onclick = ev => {
let n = number.value * 1;
let worker = new Worker('worker.js');
console.log('主线程向子线程发送消息');
worker.postMessage(n);
worker.onmessage = event => {
console.log('主线程接受到子线程发来的消息');
alert(event.data);
}
}
</script>
</body>

worker.js文件(分线程)代码

1
2
3
4
5
6
7
8
9
10
11
function fibonacci(n) {
return n<=2 ? 1 : fibonacci(n-1) + fibonacci(n-2);
}

var onmessage = event => {
let n = event.data;
console.log('子线程接收到主线程发送的消息');
let result = fibonacci(n);
postMessage(result);
console.log('子线程向主线程发送消息');
}

Web Workers 不足

  • worker 内代码不能操作 DOM (更新 UI)
  • 不能跨域加载 JS
  • 不是每个浏览器都支持这个新特性
-------------本文结束感谢您的阅读-------------
Fork me on GitHub