TIP
在介绍线程之前,会先声明进程
, 进程线程的关系
,在了解了俩者关系之后,再次思考线程带来的一些问题;
进程与线程
进程(Process)是指在操作系统中正在运行的一个程序实例。以下是进程的一些关键特性:
- 独立性:每个进程都有独立的内存空间,这意味着一个进程的执行不会直接影响到另一个进程。
- 资源分配:操作系统为每个进程分配资源,如 CPU 时间、内存和文件句柄等。
- 并发执行:多个进程可以同时运行在多核处理器上,实现真正的并行处理;在单核处理器上,通过时间片轮转等方式实现并发执行的假象。
- 通信机制:进程间可以通过多种方式通信,如管道(Pipe)、消息队列(Message Queue)、共享内存(Shared Memory)等。
- 生命周期管理:进程有其生命周期,包括创建、就绪、运行、阻塞和终止等状态。
在nodejs
中,我们经常使用child_process
创建一个子进程去处理复杂的应用场景;
const { spawn } = require("child_process");
const child = spawn("node", [
"-e",
'setInterval(() => console.log("hello"), 1000)',
]);
child.stdout.on("data", data => {
console.log(data.toString());
});
child.on("close", () => {
console.log("child process exit");
});
setTimeout(() => {
child.kill();
}, 5000);
然而在浏览器中的进程
是什么样的;
在计算机中,会单独开辟一块内存空间给到浏览器,因为计算机每时每刻都在处理其他事情,简单的说浏览器就是计算机中的一个子进程之一,因为在windows
中打开任务管理器,你就会发现浏览器在其中;
从图中就可以看出每个应用占用的内存空间是不一样的;进程与进程之间是允许通信的,但是是需要被同意的;例如:微信与 qq 通信,qq 想要通信给微信,那么微信需要支持并同意通信;
而对于浏览器而言,也是有子进程与线程的,当打开浏览器不论首页是百度
还是Google
主进程已经开始了,那么引发了一个思考?
浏览器处理了很多东西,为什么不会卡死呢?
是因为: 浏览器的渲染引擎是多线程的, 在处理不同的任务例如network, gpu, 渲染你写的页面
等等都会交给不同的子进程,每个子进程有自己的 id,并且每个子进程都是多线程的;例如下图:
那么就拿渲染主进程来说,如果渲染引擎是单线程,你可以想象的到,页面打开速度会非常慢,因为浏览器需要等待渲染引擎处理完一个任务再处理下一个任务,例如:同步任务
, 一口气区加载js
,而有的js
异常可能会发生阻塞问题,那么浏览器就得一直等,直到加载完毕才继续往下执行,这样渲染速度就会非常慢,所以浏览器就使用了多线程,每个线程负责处理不同的任务,这样渲染速度就会快很多;
浏览器的渲染主进程中,又有多个子线程区处理不同的任务;例如:计时器线程
, 网络线程
等等;
如果渲染进程
为单线程
那么会引发什么问题?
下面会介绍在js
中单线程
引发的问题, 如果了解其原理之后那么就可以解释如果渲染进程为单线程的问题?
同步与异步
在计算机中,同步和异步是两种不同的编程概念,它们在处理任务时具有不同的行为方式。
- 同步:同步是指在某个任务执行完成之前,不能继续执行其他任务。在计算机中,同步是指在某个任务执行完成之前,不能继续执行其他任务。例如,在读取文件时,如果文件正在被其他进程修改,那么读取操作将一直等待,直到文件被修改完成。
- 异步:异步是指在某个任务执行完成之后,可以继续执行其他任务。在计算机中,异步是指在某个任务执行完成之后,可以继续执行其他任务。例如,在读取文件时,如果文件正在被其他进程修改,那么读取操作将不会阻塞,而是将文件读取操作放入一个队列中,当文件被修改完成时,队列中的任务将被执行。
一张图描述了:
上面图片大概介绍同步与异步的区别,
// 大量同步任务带来的影响?
function delay(time) {
const start = Date.now();
while (Date.now() - start < time) {}
}
console.log("先触发");
delay(3 * 1000);
document.write("后触发");
// 代码解释:
// 1. 代码为同步代码。
// 2. 首先执行“先触发”, 等待 3s 之后才会渲染页面;
但是在这 3s 中你会发现页面出现了短暂的卡死现象;避免出现这样的问题,那么就需要将其改为异步
function delay(time) {
console.log(2);
return new Promise(resolve => {
setTimeout(resolve, time);
});
}
console.log("先触发");
delay(3 * 1000).then(() => {
document.write("后触发");
});
// 代码解释:
// 1. 代码为异步代码。
// 2. 首先执行“先触发”, 在等待 3s 的时间内,可以操作其他任务,不会出现阻塞问题带来的卡死;
事件循环与队列
事件循环(Event Loop)是 JavaScript 运行时环境(Runtime Environment)中的一个重要概念,它负责管理 JavaScript 代码的执行顺序。事件循环是 JavaScript 引擎和 JavaScript 运行时环境之间一个重要的交互机制,它负责将 JavaScript 代码中的异步任务与同步任务进行协调,确保代码的顺序执行。
事件循环的运行机制如下:
- 创建一个任务队列(Task Queue):任务队列用于存储待执行的任务。
- 创建一个事件队列(Event Queue):事件队列用于存储待处理的事件。
- JavaScript 引擎会从任务队列中取出一个任务,并将其放入执行栈(Execution Stack)中。
- 当执行栈中的任务执行完毕,JavaScript 引擎会检查事件队列中是否有新的事件。
- 如果有新的事件,JavaScript 引擎会将其从事件队列中取出,并将其放入执行栈中,然后继续执行任务。
- 重复步骤 4 和 5,直到任务队列和事件队列都为空。
在 JavaScript 中,事件循环的实现主要依赖于 JavaScript 引擎和宿主环境(例如浏览器)提供的 API。以下是 JavaScript 事件循环的简单介绍:
宏任务和微任务:JavaScript 中有两种类型的任务:宏任务和微任务。
- 宏任务包括
script
、setTimeout
、setInterval
、setImmediate
、I/O
、UI rendering
等, - 而微任务包括
Promise
、process.nextTick
等。
- 宏任务包括
事件循环的运行机制:事件循环会按照以下顺序运行:
- 首先,执行栈中的所有同步任务。
- 然后,将所有微任务放入微任务队列中,并依次执行。
- 最后,将所有宏任务放入宏任务队列中,并依次执行。
浏览器和 Node.js 中的事件循环机制:在浏览器中,事件循环会一直运行,直到所有宏任务和微任务都执行完毕。在 Node.js 中,事件循环会在 Node.js 进程退出之前一直运行,直到所有宏任务和微任务都执行完毕。
事件循环与队列如图:
切记事件循环的执行顺序,优先级从高至低为:全局 > 微队列 > 交互队列 > 延迟队列
,具体执行顺序可参考如下图: