React18源码: React调度中的3种优先级类型和Lane的位运算

news/2024/7/15 17:39:53 标签: react.js, 前端, 前端框架

优先级类型

  • React内部对于优先级的管理,贯穿运作流程的4个阶段(从输入到输出),根据其功能的不同,可以分为3种类型:
    • 1 )fiber优先级(LanePriority)
      • 位于 react-reconciler包,也就是Lane(车道模型)
    • 2 )调度优先级(SchedulerPriority)
      • 位于scheduler包
    • 3 )优先级等级(ReactPriorityLevel)
      • 位于react-reconciler包中的 SchedulerWithReactIntegration.js
      • 负责上述2套优先级体系的转换.
  • Lane 是在 react@17.0.0的新特性.

Lane(车道模型)

  • 英文单词lane翻译成中文表示"车道,航道"的意思,所以很多文章都将Lanes模型称为车道模型

  • Lane模型的源码在 ReactFiberLane.js,源码中大量使用了位运算

  • 首先引入对Lane的解释, 这里简单概括如下:

    • 1 )Lane类型被定义为二进制变量,利用了位掩码的特性,在频繁运算的时候占用内存少,计算速度快.
      • Lane和Lanes就是单数和复数的关系,代表单个任务的定义为Lane,代表多个任务的定义为Lanes
    • 2 )Lane是对于expirationTime的重构,以前使用expirationTime表示的字段,都改为了lane
      renderExpirationTime -> renderlanes
      update.expirationTime -> update.lane
      fiber.expirationTime -> fiber.lanes
      fiber.childExpirationTime -> fiber.childLanes
      root.firstPendingTime and root.lastPendingTime -> fiber.pendingLanes
      
    • 3 )使用Lanes模型相比expirationTime模型的优势
      • Lanes把任务优先级从批量任务中分离出来
      • 可以更方便的判断单个任务与批量任务的优先级是否重叠
      // 判断:单task与batchTask的优先级是否重叠
      // 1.通过expirationTime判断
      const isTaskIncludedInBatch = priorityOfTask >= priorityOfBatch;
      // 2.通过Lanes判断
      const isTaskIncludedInBatch =(task & batchOfTasks) !== 0;
      
      // 当同时处理一组任务,该组内有多个任务,且每个任务的优先级不一致
      // 1. 如果通过expirationTime判断,需要维护一个范围(在Lane重构之前,源码中就是这样比较的)
      const isTaskIncludedInBatch =
        taskPriority <= highestPriorityInRange &&
        taskPriority >= lowestPriorityInRange;
      // 2. 通过Lanes判断
      const isTaskIncludedInBatch = (task & batchOfTasks) !== 0;
      
  • Lanes使用单个32位二进制变量即可代表多个不同的任务

  • 也就是说一个变量即可代表一个组(group)

  • 如果要在一个group中分离出单个task,非常容易

  • 在expirationTime模型设计之初,react体系中还没有 Suspense 异步渲染的概念

  • 现在有如下场景:有3个任务,其优先级A>B>C,正常来讲只需要按照优先级顺序执行就可以了

  • 但是现在情况变了:A和C任务是CPU密集型,而B是IO密集型(Suspense会调用远程api,算是IO任务)

  • 即A(cpu)>B(IO)>C(cpu).此时的需求需要将任务B从group中分离出来,先处理cpu任务A和C

    //从group中删除或增加task
    
    // 通过expirationTime实现
    // 维护一个链表,按照单个task的优先级顺序进行插入
    // 删除单个task(从链表中删除一个元素)
    task. prev.next = task.next;
    //2)增加单个task(需要对比当前task的优先级,插入到链表正确的位置上)
    let current = queue;
    while (task.expirationTime >= current.expirationTime) {
      current = current.next;
    }
    task.next = current.next;
    current.next = task;
    //3)比较task是否在group中
    const isTaskIncludedInBatch =
      taskPriority <= highestPriorityInRange &&
      taskPriority >= lowestPriorityInRang;
    //2.通过Lanes实现
    //1)删除单个task
    batchOfTasks &= ~task;
    //2)增加单个task
    batchOfTasks |= task;
    //3)比较task是否在group中
    const isTaskIncludedInBatch = (task & batchOfTasks) !== 0;
    
  • Lanes是一个不透明的类型,只能在ReactFiberLane.js这个模块中维护

  • 如果要在其他文件中使用,只能通过 ReactFiberLane.js 中提供的工具函数来使用

  • 分析车道模型的源码(ReactFiberLane.js中),可以得到如下结论:

    • 1.可以使用的比特位一共有31位
    • 2.共定义了18种车道(Lane/Lanes)变量,每一个变量占有1个或多个比特位,分别定义为Lane和Lanes类型.
    • 3.每一种车道(Lane/Lanes)都有对应的优先级,所以源码中定义了18种优先级(LanePriority).
    • 4.占有低位比特位的Lane变量对应的优先级越高
      • 最高优先级为 SynclanePriority 对应的车道为
        • Synclane = 0b0000000000000000000000000000001
      • 最低优先级为 OffscreenLanePriority 对应的车道为
        • OffscreenLane = 0b1000000000000000000000000000000

位运算

  • 什么是位运算?程序中的所有数在计算机内存中都是以二进制的形式储存的。

  • 位运算就是直接对整数在内存中的二进制位进行操作。

  • 比如

    • 0 在二进制中用 0 表示,我们用 0000 代表;
    • 1 在二进制中用 1 表示,我们用 0001 代表;
  • 那么先看两个位运算符号 & 和 |

    • & 对于每一个比特位,两个操作数都为 1 时,结果为 1,否则为 0
    • | 对于每一个比特位,两个操作数都为0时,结果为 0,否则为 1
    • 我们看一下两个 1 & 0 和 1 | 0
    • 如上 1 & 0 = 0, 1 | 0 = 1
  • 参考

      0       0000
      1       0001
      -------------
      0 & 1 = 0000 = 0
    
  • 再参考

    0       0000
    1       0001
    ----------------
    0 | 1 = 0001 = 1
    

使用一张表格详细说明

运算符用法描述
与 &a & b如果两位都是1则设置每位为1
或 | a | b 如果两位之一为1则设置每位为1
异或 ^a^b如果两位只有一位为1则设置每位为1
非 ~~ a反转操作数的比特位, 即0变成1, 1变成0
左移(<<)a << b 将a的二进制形式向左移b(< 32)比特位, 右边用0填充
有符号右移(>>)a>>b将a的二进制形式向右移b(<32)比特位,丢弃被移除的位,左侧以最高位来填充
无符号右移(>>>)a>>>b将a的二进制形式向右移b(<32)比特位,丢弃被移除的位,并用0在左侧填充

位运算的一个使用场景

  • 比如有一个场景下,会有很多状态常量A,B,C…,这些状态在整个应用中在一些关键节点中做流程控制
  • 比如:
    if(value === A) {
      // TODO..
    }
    
  • 如上判断value等于常量A,那么进入到if的条件语句中
  • 此时是value属性是简单的一对一关系,但是实际场景下value可能是好几个枚举常量的集合
  • 也就是一对多的关系,那么此时value可能同时代表A和B两个属性
  • 如下图所示:
  • 这时候,如果按照下面的代码来写,就会很麻烦
    if(value === A || value === B) {
      // TODO..
    }
    
  • 此时的问题就是如何用一个value表示A和B两个属性的集合,这个时候位运算就派上用场了
  • 因为可以把一些状态常量用32位的二进制来表示(这里也可以用其他进制),比如:
    const A = 0b0000000000000000000000000000001 // 优先级最高
    const B = 0b0000000000000000000000000000010
    const C = 0b0000000000000000000000000000100 // 优先级最低
    
  • 通过移位的方式让每一个常量都单独占一位,这样在判断一个属性是否包含常量的时候
  • 可以根据当前位数的1和0来判断
  • 这样如果一个值即代表A又代表B那么就可以通过位运算的 | 来处理
  • 就有 AB = A | B = 0b0000000000000000000000000000011
  • 那么如果把AB的值赋予给value,那么此时的value就可以用来代表A和B
  • 此时当然不能直接通过等于或者恒等来判断value是否为A或者B,此时就可以通过&来判断。具体实现如下:
    const A = 0b0000000000000000000000000000001
    const B = 0b0000000000000000000000000000010
    const C = 0b0000000000000000000000000000100
    const N = 0b0000000000000000000000000000000
    const value = A | B
    console.log((value & A ) !== N) // true
    console.log((value & B ) !== N) // true
    console.log((value & C ) !== N ) // false
    

位运算在 react 中的应用

export const NoLanes = /*                */ 0b0000000000000000000000000000000;
const SyncLane = /*                      */ 0b0000000000000000000000000000001;

const InputContinuousHydrationLane = /*  */ 0b0000000000000000000000000000010;
const InputContinuousLane = /*           */ 0b0000000000000000000000000000100;

const DefaultHydrationLane = /*          */ 0b0000000000000000000000000001000;
const DefaultLane = /*                   */ 0b0000000000000000000000000010000;

const TransitionHydrationLane = /*       */ 0b0000000000000000000000000100000;
const TransitionLane = /*                */ 0b0000000000000000000000001000000;
  • 如上 SyncLane 代表的数值是1,它却是最高的优先级

  • 也即是说lane的代表的数值越小,此次更新的优先级就越大

  • 在新版本的React中,还有一个新特性,就是render阶段可能被中断,在这个期间会产生一个更高优先级的任务

  • 那么会再次更新lane属性,这样多个更新就会合并,这样一个lane可能需要表现出多个更新优先级

  • 我们来看一下React是如何通过位运算分离出优先级的

    function getHighestPriorityLane(lanes) {
      return lanes & -lanes;
    }
    
  • 如上就是通过 lanes & -lanes 分离出最高优先级的任务的,我们来看一下具体的流程

    • 比如SyncLane和InputContinuousLane合并之后的任务优先级lane为
    • SyncLane = 0b0000000000000000000000000000001
    • InputContinuousLane = 0b0000000000000000000000000000100
    • lane = SyncLane|InputContinuousLane
    • lane = 0b0000000000000000000000000000101
    • 那么通过 lanes & -lanes 分离出 SyncLane
    • 首先我们看一下 -lanes,在二进制中需要用补码表示为:
    • -lane = 0b1111111111111111111111111111011
    • 那么接下来执行 lanes & -lanes 看一下,& 的逻辑是如果两位都是1则设置改位为1,否则为0
    • 那么 lane & -lane, 只有一位(最后一位)全是1,所有合并后的内容为:
    • lane & -lane = 0b0000000000000000000000000000001
  • 可以看得出来lane&-lane的结果是SyncLane,所以通过lane&-lane就能分离出最高优先级的任务

    const SyncLane = 0b0000000000000000000000000000001
    const InputContinuousLane = 0b0000000000000000000000000000100
    const lane = SyncLane | InputContinuousLane
    console.log((lane & -lane) === SyncLane ) // true
    

http://www.niftyadmin.cn/n/5390757.html

相关文章

Python 读取txt中的汉字报错

Python读取txt中的汉字报错&#xff1a;UnicodeDecodeError: ‘gbk’ codec can’t decode byte 0xa7 in position 4: illegal multibyte sequence 举例&#xff1a; fileE:/0_MyWork/python_programm/children_name/strich7.txtwith open(file, "r") as file_7str…

五种多目标优化算法(MOAHA、MOGWO、NSWOA、MOPSO、NSGA2)性能对比(提供MATLAB代码)

一、5种多目标优化算法简介 多目标优化算法是用于解决具有多个目标函数的优化问题的一类算法。其求解流程通常包括以下几个步骤&#xff1a; 1. 定义问题&#xff1a;首先需要明确问题的目标函数和约束条件。多目标优化问题通常涉及多个目标函数&#xff0c;这些目标函数可能…

vue页面基本增删改查

练手项目vue页面 新手前端轻喷&#xff1a; 效果如下 1、2两个部分组成&#xff1a; 对应代码中 element-ui中的 el-form 和 el-table 照着抄呗&#xff0c;硬着头皮来&#xff01; 建议&#xff1a;认真读一遍你用的组件 那上边简单得列表举例&#xff1a; 建议大家手敲一个…

2-24算法习题总结

贪心算法 409. 最长回文串 题目: 给定一个包含大写字母和小写字母的字符串 s &#xff0c;返回 通过这些字母构造成的 最长的回文串 。 在构造过程中&#xff0c;请注意 区分大小写 。比如 “Aa” 不能当做一个回文字符串。 示例 1: 输入 s "abccccdd"输出 7…

网络互联设备-传输设备

网络互联设备分为两类&#xff1a; 1、网络传输介质 2、常见网络设备 网络的传输介质&#xff1a;由大量的同轴电缆和光纤电缆以及无线连接组成的。 同轴电缆&#xff1a; 同轴电缆的结构 以太网应用中粗同轴电缆的属性 细缆连在电缆的接插件(Bayonet Nutconnector&#xff0…

STP基本计算过程——选举网段的指定端口

点赞关注&#xff0c;持续更新STP专题 选举网段的指定端口 STP为每个网段选出一个指定端口&#xff08;Designated Port&#xff09;&#xff0c;指定端口为每个网段转发发往根交换机方向的数据&#xff0c;并且转发由根交换机方向发往该网段的数据。指定端口所在的交换机称为…

Nginx 性能优化

Nginx 性能优化 1. 当前系统结构瓶颈2. 了解业务模式3. 操作系统与 Nginx 性能优化3.1. 系统文件句柄数3.1.1. 系统全局性修改和用户局部性修改3.1.2. 进程局部性修改 3.2. CPU 的亲和性/亲缘性配置&#xff08;了解&#xff09; 4. Nginx 通用配置优化 1. 当前系统结构瓶颈 首…

《Docker 简易速速上手小册》第3章 Dockerfile 与镜像构建(2024 最新版)

文章目录 3.1 编写 Dockerfile3.1.1 重点基础知识3.1.2 重点案例&#xff1a;创建简单 Python 应用的 Docker 镜像3.1.3 拓展案例 1&#xff1a;Dockerfile 优化3.1.4 拓展案例 2&#xff1a;多阶段构建 3.2 构建流程深入解析3.2.1 重点基础知识3.2.2 重点案例&#xff1a;构建…