线程与进程
进程
线程
- 是进程内的一个独立执行单元
- 是程序执行的一个完整流程
- 是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)
事件循环模型的运转流程
- 执行执行栈中的初始化代码,将事件回调函数交给对应模块管理。
- 当事件发生时, 管理模块会将回调函数及其数据添加到回调列队(callback queue)中。
- 只有当执行栈中的初始化代码执行完后(可能要一定时间), 才会遍历读取回调队列中的回调函数并执行。
定时器问题
通过上面介绍过的事件循环运转流程,可以得知,定时器(setTimeout)回调函数只有在执行栈中的初始化代码全部执行完后才执行。因此,定时器并不能保证真正定时执行,如果在主线程在启动定时器之后执行了一个长时间的操作(时间超过定时器设置的时间),就会导致定时器回调函数延时处理。
Web Workers
传统 JavaScript 是单线程运行的,HTML5 新推出了一个 Web Worker 接口,可以实现在分线程中执行一个单独的 js 文件。
Web Workers是一种机制,通过它可以使一个脚本操作在与Web应用程序的主执行线程分离的后台线程中运行。这样做的优点是可以在单独的线程中执行繁琐的处理,让主(通常是UI)线程运行而不被阻塞/减慢。
一个 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
11function 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
- 不是每个浏览器都支持这个新特性