type
status
date
slug
summary
tags
category
icon
password
字节前端面试的时候问我线程和进程的异同,进而问我有没有用过协程,我说没有。其实前端领域用到挺多协程的知识,当时没有复习到。
进程、线程和协程

进程
一个进程就是一个程序,它是资源分配的最小单位。同一时刻执行的进程数不会超过核心数。单核CPU为啥能执行多个进程?因为单核CPU可以极快地在进程间来回切换执行。
线程
线程能共享进程的大部分资源,线程和线程之间就好比进程内任务和任务之间的关系。它是程序执行过程中最小的单元。
协程
协程是一种轻量型的线程。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。协程的本质是个单线程,它不能同时将单个CPU的多个核用上,协程需要和进程配合才能运行在多CPU上。
执行体 | 拥有资源 | 调度 | 系统开销 | 通信 |
进程 | 资源分配的基本单位 | 线程的切换不会引起进程切换 | 开销远大于创建或撤销线程时的开销 | 借助IPC |
线程 | 不拥有资源,线程可以访问隶属进程的资源 | 线程是独立调度的基本单位 | 切换时只需保存和设置少量寄存器内容,开销很小 | 直接读写同一进程中的数据进行通信 |
协程 | 不拥有资源,协程可以访问隶属线程的资源 | 调度完全由用户控制 | 上下文的切换非常快 | 直接读写同一线程中的数据进行通信 |
并发和并行
对于协程来说执行时是并行的,多个协程执行的关系并不是真正意义上的同时执行。
并行
对于单核CPU来说,在恰当的时机将CPU时间片分配给不同的任务。因为CPU执行速度极快,给人的感觉就是在同时执行两个任务。这样可以避免长任务长时间占据CPU,使后面的任务饥渴。

并发
并发针对多个CPU,多个CPU在同一时刻同时执行多个任务,是真正意义上的同时执行多个任务。

浏览器执行环境
JavaScript执行位置
从线程角度来看,浏览器执行环境可以分为以下几个线程:
- 主线程:主线程是浏览器中的核心线程,负责处理用户的输入、渲染页面、执行JavaScript代码等任务。在主线程中,JavaScript代码是单线程执行的,即一次只能执行一个任务,这也是JavaScript的特点之一。
- GUI线程:GUI线程负责渲染页面,包括解析HTML、CSS,构建DOM树和渲染页面等任务。GUI线程与主线程是互斥的,即在GUI线程执行时,主线程会暂停执行,等待GUI线程完成后再继续执行。
- 事件线程:事件线程负责处理用户的交互事件,如鼠标点击、键盘输入等。当用户触发一个事件时,事件线程会将该事件加入到事件队列中,等待主线程空闲时执行。
- 定时器线程:定时器线程负责处理定时器事件,如setTimeout和setInterval等。当定时器事件到达指定的时间时,定时器线程会将该事件加入到事件队列中,等待主线程空闲时执行。
- Web Worker线程:Web Worker线程是一个独立的线程,可以并行执行JavaScript代码,不会影响主线程的执行。Web Worker线程可以用于执行计算密集型的任务,如图像处理、数据分析等。
需要注意的是,JavaScript代码只能在主线程中执行,其他线程只能通过事件队列与主线程通信。当其他线程需要与主线程通信时,它们会将事件加入到事件队列中,等待主线程空闲时执行。因此,在编写JavaScript代码时,需要注意避免长时间的计算和阻塞主线程的执行,以免影响用户的体验。
JS线程就像单行道,JS解析、执行、绘制、事件处理都在这个线程上。任何一个任务执行事件过长,都会阻塞后面任务的执行。因此提出一种及时让出执行权的方法,协程就这样应运而生了。

协程在前端中的体现
在前端中,yield关键字可以用来暂停协程的执行,并将控制权交给调用方。yield操作返回一个值,这个值可以是任何JavaScript类型。调用方可以通过next方法来恢复协程的执行,并将一个值传递给协程。
下面是一个使用yield实现协程的例子:
在上面的例子中,使用Generator函数创建了一个协程,使用yield关键字来暂停协程的执行,并返回一个递增的数字。在每次调用next方法时,协程会从上一次暂停的位置继续执行,并返回下一个数字。
当然yield只是协程的一个应用。协程的调度,任务的定义,让出时间片的策略都可以由用户层来自定义,下面讲讲React中Fiber调度方法。
什么是Fiber?
Fiber原意是纤维的意思,注意别读错了,特别影响面试官心情。

Fiber架构在React中可以理解为:
Fiber架构 = Fiber节点 + Fiber调度算法
Fiber节点
在React中,每个Fiber节点都包含了一些指针,用于构成Fiber节点之间的链表,以及在调和过程中记录节点的状态和位置。下面是Fiber节点中常用的指针:
- child:指向当前节点的第一个子节点
- sibling:指向当前节点的下一个兄弟节点
- return:指向当前节点的父节点
这些指针构成了Fiber节点之间的链表关系,同时也记录了节点在组件树中的位置和状态,为React的调和过程提供了基础。

React的Fiber改造
在React中,组件树是由多个组件构成的,每个组件都有自己的状态和属性。当组件的状态或属性发生变化时,React会重新计算组件树,并将新的树与旧的树进行比较,以确定哪些节点需要更新。这个过程被称为调和(reconciliation)。
在React 15及其以下版本中,调和算法是基于递归的。当React需要更新组件树时,它会从根节点开始递归遍历整个树,找到需要更新的节点。这种算法虽然简单易懂,但是在组件树比较庞大时,会导致性能问题。

为了解决这个问题,React引入了Fiber节点。Fiber节点是一种轻量级的、可中断的执行单元,它可以在调和过程中暂停、恢复和重新启动。这意味着React可以在调和过程中优先处理一些高优先级的任务,而不必等待整个树的遍历完成。

Fiber节点的引入,使得React的调和算法更加高效和灵活,能够更好地应对复杂的组件树和高并发的场景。同时,Fiber节点也为React未来的发展提供了更多的可能性,例如实现React的异步渲染和增量更新等功能。
Fiber时间分片原理
requestIdleCallback
requestIdleCallback是浏览器提供的一个API,用于在浏览器空闲时执行任务。React利用了这个API实现了Fiber时间分片,从而提高了页面的响应性和流畅性。
requestIdleCallback的使用方法如下:
其中,callback是一个回调函数,用于在浏览器空闲时执行任务;options是一个可选的配置对象,用于设置任务的超时时间和优先级等。
执行流程
- 任务拆分:当React需要更新组件树时,它会将任务拆分成多个小任务,每个小任务的执行时间应该在16ms以内,以保证页面的响应性能。
- 任务优先级:React会为每个小任务设置一个优先级,高优先级的任务会优先执行,以保证页面的流畅性。React中共有5个优先级,分别是:
- Immediate:最高优先级,用于处理紧急的任务,如用户输入等;
- User Blocking:用户阻塞优先级,用于处理需要立即响应用户的任务,如动画等;
- Normal:普通优先级,用于处理常规的任务,如组件的更新等;
- Low:低优先级,用于处理不紧急的任务,如预加载等;
- Idle:最低优先级,用于处理空闲时间的任务,如缓存清理等。
- 任务调度:React会根据任务的优先级,使用调度器来安排任务的执行顺序。调度器会根据当前任务的优先级和系统的空闲时间,来决定下一个要执行的任务。
- 中断恢复:当一个小任务执行了一段时间后,React会检查是否有更高优先级的任务需要执行。如果有,React会中断当前任务,并将任务状态保存到Fiber节点中,然后执行更高优先级的任务。当更高优先级的任务执行完毕后,React会恢复之前的任务,继续执行。
- 副作用收集:在任务执行过程中,React会记录所有的副作用,如DOM操作、网络请求等。当所有任务执行完毕后,React会根据副作用的类型和顺序,将它们合并成一个批次,然后一次性执行,以减少对DOM的操作次数。
Fiber时间分片的原理,是通过将一次耗时较长的任务拆分成多个小任务,然后根据任务的优先级和系统的空闲时间,来安排任务的执行顺序,以提高页面的响应性和流畅性。

- 作者:比尔盖子
- 链接:https://www.connorshen.site/article/650f1f5c-2386-4213-a64f-1b4d8ddcf369
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。