什么是进程和线程?
进程(Process) 是操作系统分配资源的基本单位。每一个运行中的程序都是一个进程。进程之间相互独立,每个进程拥有自己独立的内存空间和系统资源。
线程(Thread) 是操作系统能够进行运算调度的最小单位,是进程中的一个执行流。一个进程可以包含多个线程,这些线程共享进程的内存和资源,但每个线程有自己的执行栈和程序计数器。
例如在我的任务管理器中,左侧“名称”一栏列出了当前系统中正在运行的各个应用程序和服务
图中每一项就是一个进程,它们分别代表着一个独立运行的程序。 仔细看我们会发现有些应用后面有括号,比如:
- Microsoft Edge (19)
- Google Chrome (13)
- Doubao (14)
这表示这些应用当前有多个进程在运行。例如,Chrome 浏览器有 13 个进程,这正是现代浏览器多进程架构的体现(每个标签页、插件、GPU等都可能单独分配一个进程)。
什么是多进程和多线程?
多进程是指一个程序可以同时运行多个相互独立的进程,每个进程拥有自己的资源和内存空间
而多线程则是指一个进程内部可以同时运行多个线程,这些线程共享进程的资源,但各自独立执行任务。
为什么需要多进程和多线程?
-
多进程:提升程序的稳定性和安全性。比如浏览器采用多进程架构,一个页面崩溃不会影响其他页面。
-
多线程:提升程序的执行效率。多个线程可以同时处理不同的任务,比如同时加载图片、渲染页面、响应用户操作。
浏览器中的多进程与多线程
在现代浏览器(如 Chrome)的多进程架构下,每个页面通常由一个独立的渲染进程负责。而在每个渲染进程内部,又包含了多个线程协同工作。
而每个渲染进程内部又包含多个线程:
-
主线程(UI线程):负责解析 HTML、CSS、执行 JavaScript、页面布局与绘制等,是最核心的线程。
-
GUI 渲染线程:专门负责页面的渲染和绘制,包括将主线程生成的渲染树(Render Tree)绘制到屏幕上。
-
JS 引擎线程:专门负责 JavaScript 代码的解析与执行(如 V8 引擎)。
-
事件触发线程、定时器线程、网络线程等。
浏览器主进程对多个渲染进程的管理和各自的分工协作模式可以用这张图简单表示:
flowchart TD
A["浏览器主进程"] --> B["渲染进程1"]
A --> C["渲染进程2"]
B --> D["主线程"]
B --> E["GUI渲染线程"]
B --> F["JS引擎线程"]
B --> G["网络线程"]
C --> H["主线程"]
C --> I["GUI渲染线程"]
渲染进程通过多线程协作,将页面解析、渲染、脚本执行、事件处理、网络请求等任务分工处理。而主线程负责核心调度,其他线程负责各自的专职任务,最终通过任务队列和事件循环机制实现高效协作。
----------------------------------------------我是分割线------------------------------------------------------
假设你在图书馆自习:
1.而你正在专心学习,这就像同步任务,主线程只做一件事。
2.突然你想到要借一本书,立刻去前台预约,这个动作很快完成,类似于微任务,会在当前任务结束后马上处理。
3.预约后你继续自习,等待管理员去找书,这个等待过程就像宏任务,需要等一段时间后才能处理。
4.当管理员通知你去取书时,你必须暂停自习去取书,这就是主线程去执行宏任务。
什么是同步任务,宏任务和微任务?
同步任务,即按照代码顺序依次执行的任务。 (如变量声明、循环、函数调用等)。
宏任务,即需要排队等待执行的大任务,比如定时器和事件回调。 常见的有:
setTimeout、setInterval。- DOM 事件(如
click、scroll)。 fetch、XMLHttpRequest的回调
微任务,是在当前任务结束后、下一个任务开始前立即执行的小任务。常见的有:
Promise.then、Promise.catch、Promise.finally。queueMicrotask()、MutationObserver。
接着让我们来看看这一段代码:
console.log('同步任务1'); // 同步任务
setTimeout(() => {
console.log('宏任务'); // 宏任务
}, 0);
Promise.resolve().then(() => {
console.log('微任务'); // 微任务
});
console.log('同步任务2'); // 同步任务
- 其中同步任务是
console.log('同步任务1')和console.log('同步任务2')
他俩立即按顺序执行,最先输出。
- 微任务是
Promise.resolve().then(...)
在当前同步任务执行完毕后,立即执行,优先级高于宏任务。
- 宏任务是
setTimeout(..., 0)
它会被放入任务队列,等同步任务和所有微任务执行完后才执行。
由此我们可以知道,EventLoop( 即用来协调和管理这些任务执行顺序的机制 )是按照“同步任务 → 微任务 → 宏任务”的顺序调度和执行这些任务的。
结合这张图,让我们详细地梳理 EventLoop 的执行流程:
首先,事件循环(EventLoop)会从宏任务队列中取出一个宏任务开始执行。这个宏任务可能是页面的主代码块、setTimeout 回调、I/O 操作等。宏任务是事件循环每一轮的起点。
然后,当这个宏任务中的所有同步代码都执行完毕后,事件循环会检查当前是否有需要处理的微任务。这里的“执行完毕”指的是当前宏任务中的所有同步代码都已经运行结束。
其次,如果发现有微任务(比如 Promise.then、MutationObserver、queueMicrotask 等),事件循环会立即执行所有微任务。注意,这里是“所有”微任务——只要微任务队列不为空,就会一直执行,直到队列被清空为止。如果在执行微任务的过程中又产生了新的微任务,也会继续执行,直到没有新的微任务为止。
最后,当所有微任务都执行完毕后,浏览器会进行一次页面的渲染(Repaint/Render),将最新的 DOM 和样式变化展示给用户。渲染完成后,事件循环会进入下一轮,从宏任务队列中取出下一个宏任务,重复上述流程。
主线程在 Event Loop 中的工作流程
我们可以把主线程比作一个“工人”,面前有一条“任务传送带”(任务队列)。每次,工人都会从传送带上拿一个任务,完成后再拿下一个。通过这张图可以让我们容易理解:
主线程依次执行同步任务
- 执行栈中的同步任务会被依次执行,遇到异步任务时,交给异步线程处理。
异步任务交给异步线程
- 比如 setTimeout、网络请求等,主线程不会等待它们完成,而是继续执行后面的同步任务。
异步线程处理完毕后,将回调放入事件队列
- 当异步操作完成后(比如定时器到点、网络请求返回),异步线程会把对应的回调函数加入到事件队列。
主线程空闲时,从事件队列取任务执行
- 当主线程上的同步任务全部执行完毕,执行栈为空时,就会去事件队列中取出最前面的任务,放到主线程执行。
而这个过程在不断循环,这就是“事件循环”(Event Loop)。
小结
通过本文的梳理,我们系统地了解了进程与线程的区别、浏览器的多进程多线程架构,以及同步任务、宏任务、微任务在事件循环中的执行顺序。结合实际案例和图示,一步步揭开了浏览器主线程如何协调各类任务、保证页面高效响应的底层机制。