惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Tailwind CSS Blog
博客园 - Franky
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Y
Y Combinator Blog
Hugging Face - Blog
Hugging Face - Blog
博客园 - 聂微东
L
LangChain Blog
博客园_首页
Recent Announcements
Recent Announcements
月光博客
月光博客
酷 壳 – CoolShell
酷 壳 – CoolShell
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
H
Hackread – Cybersecurity News, Data Breaches, AI and More
爱范儿
爱范儿
博客园 - 叶小钗
博客园 - 【当耐特】
The Cloudflare Blog
J
Java Code Geeks
G
Google Developers Blog
云风的 BLOG
云风的 BLOG
Blog — PlanetScale
Blog — PlanetScale
博客园 - 司徒正美
aimingoo的专栏
aimingoo的专栏
A
About on SuperTechFans

蚊子的前端博客

微说 | 有的人觉得只要不考XX,一定能考好!-蚊子的前端博客 微说 | 春天来了-蚊子的前端博客 明天和意外不知道哪个先来-蚊子的前端博客 微说 | 既然错过了路口,就及时止损,重新规划路线上路-蚊子的前端博客 微说 | 2025年的出生人口数是792万-蚊子的前端博客 微说 | 工作年终总结,让写对接的接口的数量?-蚊子的前端博客 微说 | 温家宝:没有政治体制改革的成功 经济体制改革不可能进行到底-蚊子的前端博客 微说 | 违法犯罪了该不该被禁言?-蚊子的前端博客 微说 | 不明白为什么在推荐去俄罗斯旅游-蚊子的前端博客 微说 | 易中天论骗子-蚊子的前端博客 微说 | 一场大风吹散了秋天-蚊子的前端博客 微说 | 既要、又要、还要、更要!-蚊子的前端博客 又是一年的国庆雨季-蚊子的前端博客 微说 | 预估下2025年的出生人口数据-蚊子的前端博客 微说 | 可惜了我那些小时候的书本-蚊子的前端博客 微说 | 牛马有的是,驴不够了!-蚊子的前端博客 微说 | 能否有一条非户口也能高考的路-蚊子的前端博客 微说 | 小聊中医-蚊子的前端博客 微说 | 将要制作的一款新产品-蚊子的前端博客 微说 | 做人要有信-蚊子的前端博客 微说 | 好一个正义联盟-蚊子的前端博客 微说 | 都是见过吃过的主儿-蚊子的前端博客 微说 | 追求8小时工作制有错吗?-蚊子的前端博客 微说 | 如果尖锐的批评完全消失-蚊子的前端博客 微说 | 很好!-蚊子的前端博客 微说 | 封禁用户可以,但要告知具体原因-蚊子的前端博客 微说 | 面朝大海,春暖花开-蚊子的前端博客 微说 | 程序员的悲哀是什么?-蚊子的前端博客 微说 | 2025年出生人口的预测-蚊子的前端博客 前端在 LiveKit 中如何获取所有的参与者-蚊子的前端博客
React18 源码解析之 lanes 模型中的位运算-蚊子的前端博客
author · 2022-07-27 · via 蚊子的前端博客

我们解析的源码是 React18.1.0 版本,请注意版本号。React 源码学习的 GitHub 仓库地址:https://github.com/wenzi0github/react

在 lanes 模型中,我们可以通过各种的位运算,实现不同 lane 的组合,这里我们来了解下 lanes 模型中涉及到的几种。

1. 位运算的基本使用 #

位运算主要包含了按位与、按位或、异或、取反、左移、右移等操作。

1.1 左移的操作 #

在 lanes 模型,有很多对 1 的左移操作,如:

const lane = 1 << index; // index为lanes中某个1的位置,从右往左,从0开始数的位置

这段的作用是什么呢?刚开始我想反了,以为是 index 左移 1 位。 其实是数字 1 左移 index 位,那么得到的是:

const lane = 1 << index; // index为5,1<<5,1左移5位后得到的是 0b0100000

即可以得到 lanes 中 index 位置的 lane 模型,这个 lane 模型只有 index 位置是 1,方便后续在 lanes 中对 lane 进行位运算操作。

1.2 按位与的操作 #

按位与 &,是同一个位置上都为 1 时,结果才会为 1。按位与的操作,通常是用来筛选出某些 lanes 模型的,即会收窄 lanes 模型中的数据。

A&B,我们做一个形象的比喻:A 中的 1 就像汽车,0 是没有车。B 就像车道,1 是畅通,0 是有阻挡。当执行 A&B 后得到的结果就是,通过 B 车道后,到达终点的车:

A&B

如:

const nonIdlePendingLanes = pendingLanes & NonIdleLanes;

NonIdleLanes 表示未闲置任务的 lanes,pendingLanes & NonIdleLanes,即 pendingLanes 模型中,与 NonIdleLanes 模型重合的数据,都是 NonIdleLanes 范围内的,那就可以筛选出未闲置的任务。

还有,就是用来判断两个 lanes 的包含关系了,具体比较什么,还得看跟什么进行对比。

如下面的这段代码,NoLanes 的值为 0,若按位与操作后的结果不为 0,则说明 lanes 中至少了包含了一个 NonIdleLanes 类型的任务,即 lanes 和 NonIdleLanes 至少在同一个位置上都是是 1:

/**
 * 判断lanes中是否有未闲置的任务
 * @param lanes
 * @returns {boolean}
 */
export function includesNonIdleWork(lanes: Lanes) {
  return (lanes & NonIdleLanes) !== NoLanes;
}

再如下面的这段代码,我们先不管 RetryLanes 的数值和含义:

/**
 * 判断lanes中是否只包含重试任务
 * @param lanes
 * @returns {boolean}
 */
export function includesOnlyRetries(lanes: Lanes) {
  return (lanes & RetryLanes) === lanes;
}

lanes 与它按位与操作后,再跟 lanes 本身进行判断,若还是相等,说明 lanes 要么为 NoLanes(0 与任何数按位与都是 0), 要么只包含了 RetryLanes 中几个或者全部的任务。举个栗子,若 RetryLanes 的值为 0b01100,那 lanes 对应的二进制中的 1,如果有的话,只能出现在 RetryLanes 中 1 的位置。提炼一下:lanes 中 1 的位置只能是 RetryLanes 的子集。

以上面比喻的车道来解释的话:RetryLanes 中有很多畅通的车道,lanes 中凡是有车的位置,一定是在畅通的位置上,一定可以走到终点;或者畅通的车道上没车也行。

// 假设 RetryLanes = 0b01100
0b01000 & RetryLanes; // 0b01000, true
0b00100 & RetryLanes; // 0b00100, true
0b01100 & RetryLanes; // 0b01100, true
0b00000 & RetryLanes; // 0b00000, true

0b01010 & RetryLanes; // 0b01000, false

但若按位与操作后的比较对象换了,就成另一个含义了。如这段代码:

export function isSubsetOfLanes(set: Lanes, subset: Lanes | Lane) {
  return (set & subset) === subset;
}

函数 isSubsetOfLanes() 与上面的 函数 includesOnlyRetries() 区别在于,比较对象不一样。这里是按位与操作后,与后一个对象进行比较。若相等的话,则说明 set 完全包含了 subset,并且 set 其他位置可能还有 1。即 set 是 subset 的超集。若 subset 为 NoLanes 时,subset 是任何 set 的子集。

以上面比喻的车道来解释的话:subset 中畅通的车道上一定有车可以达到终点,不畅通的车道上也可以有车(但不一定);若 subset 为 NoLanes 时,即畅通的车道为 0,那 set 就可以为任意值。

1.3 按位或的操作 #

按位或的操作,是将不同的优先级组合到一起。若组合之前就已经有这个优先级了,按位或操作时,也完全没有影响。

如:

root.pendingLanes |= updateLane; // 即 root.pendingLanes = root.pendingLanes | updateLane

在 root.pendingLanes 原有基础上,再合并其他的 lane 操作。

2. 多种位运算的组合 #

lanes 模型在多种位运算的组合下,呈现了更强大的威力。

2.1 排除某个 lanes #

涉及到的运算符是 按位与 和 取反 操作。如

// 从 nonIdlePendingLanes 中排除 suspendedLanes
const nonIdleUnblockedLanes = nonIdlePendingLanes & ~suspendedLanes;

我们来拆解这个操作。取反操作的优先级比按位与高,先执行取反操作,然后再按位与操作。对 suspendedLanes 取反操作后的赛道,还能通过的,必然都不是 suspendedLanes 里的任务了。

2.2 获取当前 lanes 中的最高优先级的任务 #

最高优先级的任务,就是二进制中最右边的那个 1。这里涉及到的运算符是 按位与 和 负号,操作上是 lanes 与自己的负数进行按位与操作:

/**
 * 获取lanes中最高优先级的那个数,即最右边的那个1
 * 在位运算中,若有负数,则使用该负数的补码参与运算,
 * 位运算的文档:https://www.cnblogs.com/CoutCodes/p/12557649.html
 * 如lanes = 5 = 101 源码
 * -lanes = -5 = 011 补码
 * lanes & -lanes = 101 & 011 = 1
 * 即最右边的1代表的那个数字
 * @param lanes
 * @returns {number}
 */
export function getHighestPriorityLane(lanes: Lanes): Lane {
  return lanes & -lanes;
}

我们刚才在注释里用了一个 5(0b101)的例子,再看一个例子。若 lanes 为 12(0b1100):

  1. lanes 为 12,二进制为 0b1100;
  2. -lanes 为 -12,反码为 0b0011,补码(反码加 1 即为补码)为 0b0100(0b0011 + 1);
  3. lanes & -lanes,即 0b1100 & 0b0100,结果为 0b0100;

最后的结果为 0b0100,转为十进制的话就是 4。我们通过上面的函数计算下:

getHighestPriorityLane(12); // 4
getHighestPriorityLane(0b1100); // 4

2.3 对 lanes 的循环 #

React 源码有不少对 lanes 的循环操作,即操作 lanes 上每一个 1,如在函数 markRootSuspended() 中:

let lanes = suspendedLanes;
while (lanes > 0) {
  const index = pickArbitraryLaneIndex(lanes);
  const lane = 1 << index;

  // 将该位置的过期时间重置为初始值,即-1
  expirationTimes[index] = NoTimestamp;

  lanes &= ~lane;
}

上面的循环 while 中的操作很少,就是将 lanes 模型每个 1 的位置的过期重置为初始值。

  1. while 循环的终止条件是 lanes 为 0,那必然有对 lanes 的操作,就是: lanes &= ~lane;
  2. 每次取出一个赛道,然后对其进行操作;
  3. 最后在 lanes 中删除该赛道,进入下一个循环,直到所有的赛道都处理完毕;

4. 总结 #

熟悉这些 lanes 的位运算后,后续通过对各种优先级的组合、筛选、过滤等操作,来调度任务时,就会熟悉很多。