持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第12天,点击查看活动详情
进程与线程
进程:程序的一次执行, 它占有一片独有的内存空间。可以通过windows任务管理器查看进程。
线程:是进程内的一个独立执行单元,是程序执行的一个完整流程. 是CPU的最小的调度单元
例如:
注意:
- 应用程序必须运行在某个进程的某个线程上
- 一个进程中至少有一个运行的线程: 主线程, 进程启动后自动创建
- 一个进程中也可以同时运行多个线程, 我们会说程序是多线程运行的
- 一个进程内的数据可以供其中的多个线程直接共享
- 多个进程之间的数据是不能直接共享的
线程池(thread pool): 保存多个线程对象的容器, 实现线程对象的反复利用
相关问题:
graph LR
A(相关问题) --> B(何为多进程与多线程)
B --> C(多进程运行: 一应用程序可以同时启动多个实例运行)
B --> D(多线程: 在一个进程内, 同时有多个线程运行)
A(相关问题) -->E(比较单线程与多线程?)
E --> 多线程
多线程 -->优点-->能有效提升CPU的利用率
多线程 -->缺点-->创建多线程开销
缺点-->线程间切换开销
缺点-->死锁与状态同步问题
E --> 单线程
单线程-->F(优点)-->顺序编程简单易懂
单线程-->G(缺点)-->效率低
A(相关问题) --> JS是单线程还是多线程?
JS是单线程还是多线程? -->js是单线程运行的
JS是单线程还是多线程? -->k(但使用H5中的 Web Workers可以多线程运行)
A(相关问题) --> 浏览器运行是单线程还是多线程?-->都是多线程运行的
A(相关问题) -->浏览器运行是单进程还是多进程?
浏览器运行是单进程还是多进程? -->有的是单进程
有的是单进程 -->firefox
有的是单进程 -->老版IE
浏览器运行是单进程还是多进程? -->有的是多进程
有的是多进程 -->chrome
有的是多进程 -->新版IE
浏览器运行是单进程还是多进程? -->如何查看浏览器是否是多进程运行的呢?-->任务管理器-->进程
单核也能创建多个线程,他可以来回切换着运行,一个线程运行一下。
浏览器内核
浏览器内核是支撑浏览器运行的最核心的程序。
不同的浏览器可能不一样:
graph LR
A(内核) --> B(Chrome, Safari : webkit)
A --> C(firefox : Gecko)
A --> D(IE: Trident)
A --> E(360,搜狗等国内浏览器: Trident + webkit)
内核由很多模块组成:
- js引擎模块 : 负责js程序的编译与运行
- html,css文档解析模块 : 负责页面文本的解析
- DOM/CSS模块 : 负责dom/css在内存中的相关处理
- 布局和渲染模块 : 负责页面的布局和效果的绘制(内存中的对象)
- 定时器模块 : 负责定时器的管理
- DOM事件响应模块 : 负责事件的管理
- 网络请求模块 : 负责ajax请求
其中前四个处于主线程,后三个处于分线程。但这并不意味着后三个就在分线程上运行,前面我们提到过js是单线程的。
定时器引发的思考
==定时器并不能保证真正定时执行!==,一般会延迟一丁点(可以接受), 也有可能延迟很长时间。
例如以下代码的定时器是完全不准确的:
<script type="text/javascript">
document.getElementById('btn').onclick = function () {
var start = Date.now()
console.log('启动定时器前...')
setTimeout(function () {
console.log('定时器执行了', Date.now()-start)
}, 200)
console.log('启动定时器后...')
// 做一个长时间的工作
for (var i = 0; i < 1000000000; i++) {
}
}
</script>
定时器回调函数是在分线程执行的吗? 在主线程执行的, js是单线程的
定时器是如何实现的?
事件循环模型
JS是单线程执行的
我们可以用以下代码证明JS是单线程执行的:
<script type="text/javascript">
setTimeout(function () {
console.log('timeout 2222')
alert('22222222')
}, 2000)
setTimeout(function () {
console.log('timeout 1111')
alert('1111111')
}, 1000)
setTimeout(function () {
console.log('timeout() 00000')
}, 0)
function fn() {
console.log('fn()')
}
fn()
console.log('alert()之前')
alert('------') //暂停当前主线程的执行, 同时暂停计时, 点击确定后, 恢复程序执行和计时
console.log('alert()之后')
</script>
alert()函数有暂停当前主线程的执行, 同时暂停计时的功能
有运行结果可以知道: setTimeout()的回调函数是在主线程执行的,定时器回调函数只有在运行栈中的代码全部执行完后才有可能执行。
为什么js要用单线程模式, 而不用多线程模式? JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。
例如我们可以想象一下:我们现在有两个线程对一个div进行操作,其中一个线程是设置样式的,另一个线程是删除的。有这么一种情况它是有发生的可能性的:当运行其中一个线程进行样式设置时,突然线程切换,把div删除了,当回到原来的线程的时候就会出现错误。
我们可以将JS中的代码从另一种角度分类:
- 初始化代码
- 回调代码
js引擎执行代码的基本流程:先执行初始化代码: 包含一些特别的代码 --> 回调函数(异步执行,后面在某个时刻才会执行回调代码)
常见的回调函数:
- 设置定时器
- 绑定事件监听
- 发送ajax请求
特别注意:setTimeout()是初始化代码,它里面的回调函数才是回调代码!
事件循环模型
原理图:
WEB APIS中的三个模块负责处理对应的回调代码。从此图我们可以明显的发现这三个模块处理器(由浏览器提供)是处于分线程的位置,而JS引擎处于主线程的位置(它也只可能处于主线程的位置)
模型的2个重要组成部分:
- 事件(定时器/DOM事件/Ajax)管理模块
- 回调队列(callback queue)
模型的运转流程: 执行初始化代码, 将事件回调函数交给对应模块管理。当事件发生时, 管理模块会将回调函数及其数据添加到回调列队中,只有当初始化代码执行完后(可能要一定时间), 才会遍历读取回调队列中的回调函数执行。
相关概念:
-
执行栈(execution stack):所有的代码都是在此空间中执行的
-
任务队列(task queue)
-
消息队列(message queue)
-
事件队列(event queue)
-
上面三个队列等同于回调队列(callback queue)
-
事件轮询:从任务队列中循环取出回调函数放入执行栈中处理(一个接一个)
此时我们可以解释在线程机制与事件机制①中为什么定时器不准的原因。真正帮我们计时的是setTimeout回调处理模块,JS引擎是不会帮我们计时的。==在时间到了之后并不意味着马上执行==!而是模块把回调函数放入到回调队列中,这个时候处于一个等待执行的状态。只有在执行栈中的代码执行完了以后,才会把回调队列中的代码放到执行栈中去执行。当执行栈中的初始化代码所花时间过长的时候,我们的回调代码会晚进入执行栈,就给我们计时不准的错觉。
H5 Web Workers实现多线程
Web Workers 是 HTML5 提供的一个javascript多线程解决方案。我们可以将一些大计算量的代码交由web Worker运行而不冻结用户界面。但是子线程完全受主线程控制,==且不得操作DOM==。所以,这个新标准并没有改变JavaScript单线程的本质
使用步骤:
- 创建在分线程执行的js文件
- 在主线程中的js中发消息并设置回调
相关API
- Worker: 构造函数, 加载分线程执行的js文件
- Worker.prototype.onmessage: 用于接收另一个线程的回调函数
- Worker.prototype.postMessage: 向另一个线程发送消息
我们模拟一个场景:用分线程计算斐波那契数列,在计算的这段时间之内不冻结用户界面,可以自由操作。
主线程:
<input type="text" placeholder="数值" id="number">
<button id="btn">计算</button>
<script type="text/javascript">
var input = document.getElementById('number')
document.getElementById('btn').onclick = function () {
var number = input.value
//创建一个Worker对象
var worker = new Worker('worker.js')
// 绑定接收消息的监听
worker.onmessage = function (event) {
console.log('主线程接收分线程返回的数据: '+event.data)
alert(event.data)
}
// 向分线程发送消息
worker.postMessage(number)
console.log('主线程向分线程发送数据: '+number)
}
// console.log(this) // window
</script>
分线程:
function fibonacci(n) {
return n<=2 ? 1 : fibonacci(n-1) + fibonacci(n-2) //递归调用
}
console.log(this)
this.onmessage = function (event) {
var number = event.data
console.log('分线程接收到主线程发送的数据: '+number)
//计算
var result = fibonacci(number)
postMessage(result)
console.log('分线程向主线程返回数据: '+result)
// alert(result) alert是window的方法, 在分线程不能调用
// 分线程中的全局对象不再是window, 所以在分线程中不可能更新界面
//console是每个浏览器都提供的实现,跟window没有关系
}
注意:
- alert是window的方法, 在分线程不能调用
- 分线程中的全局对象不再是window, 所以在分线程中不可能更新界面
- console是每个浏览器都提供的实现,跟window没有关系
图解:
不足之处:
- 不能跨域加载JS
- worker内代码不能访问DOM(更新UI)
- 不是每个浏览器都支持这个新特性