会员登录 - 用户注册 - 设为首页 - 加入收藏 - 网站地图 React中的任务饥饿行为!

React中的任务饥饿行为

时间:2025-11-05 15:57:30 来源:益强数据堂 作者:域名 阅读:437次

本文是饿行在React中的高优先级任务插队机制基础上的后续延伸,先通过阅读这篇文章了解任务调度执行的饿行整体流程,有助于更快地理解本文所讲的饿行内容。

饥饿问题说到底就是饿行高优先级任务不能毫无底线地打断低优先级任务,一旦低优先级任务过期了,饿行那么他就会被提升到同步优先级去立即执行。饿行如下面的饿行例子:

我点击左面的开始按钮,开始渲染大量DOM节点,饿行完成一次正常的饿行高优先级插队任务:

而一旦左侧更新的时候去拖动右侧的元素,并在拖动事件中调用setState记录坐标,饿行介入更高优先级的饿行任务,这个时候,饿行左侧的饿行DOM更新过程会被暂停,不过当我拖动到一定时间的饿行时候,左侧的饿行任务过期了,那它就会提升到同步优先级去立即调度,完成DOM的更新(低优先级任务的lane优先级并没有变,云南idc服务商只是任务优先级提高了)。

要做到这样,React就必须用一个数据结构去存储pendingLanes中有效的lane它对应的过期时间。另外,还要不断地检查这个lane是否过期。

这就涉及到了任务过期时间的记录 以及 过期任务的检查。

lane模型过期时间的数据结构

完整的pendingLanes有31个二进制位,为了方便举例,我们缩减位数,但道理一样。

例如现在有一个lanes:

0 b 0 0 1 1 0 0 0 

那么它对应的过期时间的数据结构就是这样一个数组:

[ -1, -1, 4395.2254, 3586.2245, -1, -1, -1 ] 

在React过期时间的机制中,-1 为 NoTimestamp

即pendingLanes中每一个1的位对应过期时间数组中一个有意义的时间,过期时间数组会被存到root.expirationTimes字段。这个计算和存取以及判断是否过期的逻辑

是在markStarvedLanesAsExpired函数中,每次有任务要被调度的时候都会调用一次。企商汇

记录并检查任务过期时间

在React中的高优先级任务插队机制那篇文章中提到过,ensureRootIsScheduled函数作为统一协调任务调度的角色,它会调用markStarvedLanesAsExpired函数,目的是把当前进来的这个任务的过期时间记录到root.expirationTimes,并检查这个任务是否已经过期,若过期则将它的lane放到root.expiredLanes中。

function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {   // 获取旧任务   const existingCallbackNode = root.callbackNode;   // 记录任务的过期时间,检查是否有过期任务,有则立即将它放到root.expiredLanes,   // 便于接下来将这个任务以同步模式立即调度   markStarvedLanesAsExpired(root, currentTime);   ... } 

markStarvedLanesAsExpired函数的实现如下:

暂时不需要关注suspendedLanes和pingedLanes

export function markStarvedLanesAsExpired(   root: FiberRoot,   currentTime: number, ): void {   // 获取root.pendingLanes   const pendingLanes = root.pendingLanes;   // suspense相关   const suspendedLanes = root.suspendedLanes;   // suspense的任务被恢复的lanes   const pingedLanes = root.pingedLanes;   // 获取root上已有的过期时间   const expirationTimes = root.expirationTimes;   // 遍历待处理的lanes,检查是否到了过期时间,如果过期,   // 这个更新被视为饥饿状态,并把它的lane放到expiredLanes   let lanes = pendingLanes;   while (lanes > 0) {     /*      pickArbitraryLaneIndex是找到lanes中最靠左的那个1在lanes中的服务器托管index      也就是获取到当前这个lane在expirationTimes中对应的index      比如 0b0010,得出的index就是2,就可以去expirationTimes中获取index为2      位置上的过期时间     */     const index = pickArbitraryLaneIndex(lanes);     const lane = 1 << index;     // 上边两行的计算过程举例如下:     //   lanes = 0b0000000000000000000000000011100     //   index = 4     //       1 = 0b0000000000000000000000000000001     //  1 << 4 = 0b0000000000000000000000000001000     //    lane = 0b0000000000000000000000000001000     const expirationTime = expirationTimes[index];     if (expirationTime === NoTimestamp) {       // Found a pending lane with no expiration time. If its not suspended, or       // if its pinged, assume its CPU-bound. Compute a new expiration time       // using the current time.       // 发现一个没有过期时间并且待处理的lane,如果它没被挂起,       // 或者被触发了,那么去计算过期时间       if (         (lane & suspendedLanes) === NoLanes ||         (lane & pingedLanes) !== NoLanes       ) {         expirationTimes[index] = computeExpirationTime(lane, currentTime);       }     } else if (expirationTime <= currentTime) {       // This lane expired       // 已经过期,将lane并入到expiredLanes中,实现了将lanes标记为过期       root.expiredLanes |= lane;     }     // 将lane从lanes中删除,每循环一次删除一个,直到lanes清空成0,结束循环     lanes &= ~lane;   } } 

通过markStarvedLanesAsExpired的标记,过期任务得以被放到root.expiredLanes中在随后获取任务优先级时,会优先从root.expiredLanes中取值去计算优先级,这时得出的优先级是同步级别,因此走到下面会以同步优先级调度。实现过期任务被立即执行。

function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {   // 获取旧任务   const existingCallbackNode = root.callbackNode;   // 记录任务的过期时间,检查是否有过期任务,有则立即将它放到root.expiredLanes,   // 便于接下来将这个任务以同步模式立即调度   markStarvedLanesAsExpired(root, currentTime);   ...   // 若有任务过期,这里获取到的会是同步优先级   const newCallbackPriority = returnNextLanesPriority();   ...   // 调度一个新任务   let newCallbackNode;   if (newCallbackPriority === SyncLanePriority) {     // 过期任务以同步优先级被调度     newCallbackNode = scheduleSyncCallback(       performSyncWorkOnRoot.bind(null, root),     );   } } 

记录并检查任务是否过期

concurrent模式下的任务执行会有时间片的体现,检查并记录任务是否过期就发生在每个时间片结束交还主线程的时候。可以理解成在整个(高优先级)任务的执行期间,

持续调用ensureRootIsScheduled去做这件事,这样一旦发现有过期任务,可以立马调度。

执行任务的函数是performConcurrentWorkOnRoot,一旦因为时间片中断了任务,就会调用ensureRootIsScheduled。

function performConcurrentWorkOnRoot(root) {   ...   // 去执行更新任务的工作循环,一旦超出时间片,则会退出renderRootConcurrent   // 去执行下面的逻辑   let exitStatus = renderRootConcurrent(root, lanes);   ...   // 调用ensureRootIsScheduled去检查有无过期任务,是否需要调度过期任务   ensureRootIsScheduled(root, now());   // 更新任务未完成,return自己,方便Scheduler判断任务完成状态   if (root.callbackNode === originalCallbackNode) {     return performConcurrentWorkOnRoot.bind(null, root);   }   // 否则retutn null,表示任务已经完成,通知Scheduler停止调度   return null; } 

performConcurrentWorkOnRoot是被Scheduler持续执行的,这与Scheduler的原理相关,可以移步到我写的一篇长文帮你彻底搞懂React的调度机制原理这篇文章去了解一下,如果暂时不了解也没关系,你只需要知道它会被Scheduler在每一个时间片内都调用一次即可。

一旦时间片中断了任务,那么就会走到下面调用ensureRootIsScheduled。我们可以追问一下时间片下的fiber树构建机制,更深入的理解ensureRootIsScheduled

为什么会在时间片结束的时候调用。

这一切都要从renderRootConcurrent函数说起:

function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {   // workLoopConcurrent中判断超出时间片了,   // 那workLoopConcurrent就会从调用栈弹出,   // 走到下面的break,终止循环   // 然后走到循环下面的代码   // 就说明是被时间片打断任务了,或者fiber树直接构建完了   // 依据情况return不同的status   do {     try {       workLoopConcurrent();       break;     } catch (thrownValue) {       handleError(root, thrownValue);     }   } while (true);   if (workInProgress !== null) {       // workInProgress 不为null,说明是被时间片打断的       // return RootIncomplete说明还没完成任务     return RootIncomplete;   } else {     // 否则说明任务完成了 

renderRootConcurrent中写了一个do...while(true)的循环,目的是如果任务执行的时间未超出时间片限制(一般未5ms),那就一直执行,

直到workLoopConcurrent调用完成出栈,brake掉循环。

workLoopConcurrent中依据时间片去深度优先构建fiber树

function workLoopConcurrent() {   // 调用shouldYield判断如果超出时间片限制,那么结束循环   while (workInProgress !== null && !shouldYield()) {     performUnitOfWork(workInProgress);   } } 

所以整个持续检查过期任务过程是:一个更新任务被调度,Scheduler调用performConcurrentWorkOnRoot去执行任务,后面的步骤:

performConcurrentWorkOnRoot调用renderRootConcurrent,renderRootConcurrent去调用workLoopConcurrent执行fiber的构建任务,也就是update引起的更新任务。 当执行时间超出时间片限制之后,首先workLoopConcurrent会弹出调用栈,然后renderRootConcurrent中的do...while(true)被break掉,使得它也弹出调用栈,因此回到performConcurrentWorkOnRoot中。 performConcurrentWorkOnRoot继续往下执行,调用ensureRootIsScheduled检查有无过期任务需要被调度。 本次时间片跳出后的逻辑完成,Scheduler会再次调用performConcurrentWorkOnRoot执行任务,重复1到3的过程,也就实现了持续检查过期任务。

总结

低优先级任务的饥饿问题其实本质上还是高优先级任务插队,但是低优先级任务在被长时间的打断之后,它的优先级并没有提高,提高的根本原因是markStarvedLanesAsExpired

将过期任务的优先级放入root.expiredLanes,之后优先从expiredLanes获取任务优先级以及渲染优先级,即使pendingLanes中有更高优先级的任务,但也无法从pendingLanes中

获取到高优任务对应的任务优先级。

(责任编辑:系统运维)

上一篇:1)、下载 2)、解压 3)、进入相关目录进行以下操作 复制代码代码如下: 假如你运气好的话,一切ok,不过………..哈哈。Ubuntu默认的策略是什么库都不装,依赖的库都需要自已手工安装搞定。 一般都会出错的,那么我们来看看可能出现的问题。 4)、常见问题解决办法 复制代码代码如下: 运气好一次通过,运气不好,make pcre时会出错 复制代码代码如下: 解决方法:需要先安装libtool和gcc-c++ 复制代码代码如下: 缺少zlib库 复制代码代码如下: 解决办法:少什么就安装什么呗。 复制代码代码如下: 进入nginx目录cd nginx-1.2.2/,执行以下命令 复制代码代码如下: 大爷的,又可能报错。没有nginx,logs目录访问权限 复制代码代码如下: 解决办法: 复制代码代码如下: 现在,差不多没问题了。 复制代码代码如下: 红色部分,根据自己的路径修改。 6)、常用命令 重启nginx:service nginx restart 7)、linux常用命令 ip查看 编译 安装编译好的源码包 编辑文件 修改根限:chmod说明(u:与文件属主拥有一样的权限[a:所有人];+:增加权限;rwx:可读可写可执行) 检查是库是否安装成功 下载安装库 检查服务启动是否正常 查找openssl安装路径 更新源 更新已安装的包 sudo apt-get upgrade
下一篇:NVIDIA 358.16 —— NVIDIA 358 系列的第一个稳定版本已经发布,并对 358.09 中(测试版)做了一些修正,以及一些小的改进。NVIDIA 358 增加了一个新的 nvidia-modeset.ko 内核模块,可以配合 nvidia.ko 内核模块工作来调用 GPU 显示引擎。在以后发布版本中,nvidia-modeset.ko 内核驱动程序将被用于模式设置接口的基础,该接口由内核的直接渲染管理器(DRM)所提供。新的驱动程序也有新的 GLX 协议扩展,以及在 OpenGL 驱动中分配大量内存的系统内存分配新机制。新的 GPU GeForce 805A 和 GeForce GTX 960A 都支持。NVIDIA 358.16 也支持 X.Org 1.18 服务器和 OpenGL 4.3。如何在 Ubuntu 中安装 NVIDIA 358.16 :复制代码代码如下:它会要求你输入密码。输入密码后,密码不会显示在屏幕上,按 Enter 继续。2. 刷新并安装新的驱动程序添加 PPA 后,逐一运行下面的命令刷新软件库并安装新的驱动程序:复制代码代码如下:(假如需要的话,) 卸载:复制代码代码如下:删除所有的 nvidia 包:复制代码代码如下:最后返回菜单并重新启动:复制代码代码如下:要禁用/删除显卡驱动 PPA,点击系统设置下的软件和更新,然后导航到其他软件标签。
推荐内容
  • 这里使用Unix网络编程里面的一个小程序,该客户端建立一个到服务器的TCP连接,然后读取由服务器以直观可读格式简单地送回的当前时间和日期.复制代码代码如下:  sudo apt-get install xinetd复制代码代码如下:   (disable = yes)  ->(disable = no)sudo /etc/init.d/xinetd restart测试本机复制代码代码如下:  dig time.windows.com +short复制代码代码如下:  connect error: Connection timed out第二次复制代码代码如下:  connect error: Connection refused换个服务器[code]  dig time.nist.gov./daytimetcpcli 128.138.141.172[code]结果56953 14-10-23 16:46:39 11 0 0   0.0 UTC(NIST) *可以看直观的看出来,当地的时间为下午4点钟.
  • kubelet 配置资源预留的姿势
  • 超 400 个 IP 地址协同攻击,利用多个 SSRF 漏洞发起网络攻势
  • 数字孪生技术如何增强网络安全
  • 想知道在Linux中你正在使用的网卡是什么吗? 在Linux中很容易就找出网卡的生产商。打开一个终端并输入下面的额命令:复制代码代码如下:sudo lshw -C network假如上面的命令不能在sudo下使用,那就别用 sudo 的特权模式。它的输出看上去有点奇怪但是很有用。复制代码代码如下:   *-network        description: Wireless interface        product: BCM4360 802.11ac Wireless Network Adapter        vendor: Broadcom Corporation        physical id: 0        bus info: pci@0000:03:00.0        logical name: wlan0        version: 03        serial: 9c:f3:87:c1:5d:6a        width: 64 bits        clock: 33MHz        capabilities: busmaster caplist ethernet physical wireless        configuration: broadcast=yes driver=wl0 driverversion=6.30.223.248 (r487574) ip=192.168.1.23 latency=0 multicast=yes wireless=IEEE 802.11abg        resources: irq:18 memory:b0600000-b0607fff memory:b0400000-b05fffff如你所见,我Macbook Air上的无线网卡是BCM4360,这是一款在Ubuntu下面很容易出现无法检测无线网络问题的网卡。lshw 命令实际上是用来列出硬件的,因此命令的名字是lshw。带上网络的选项后,就会只过滤出网络硬件了。了解网卡的其他方法另外你还可以使用lspci命令来显示PCI总线上的信息。你应该使用普通用户来运行这个命令。只需要在命令行下输入:复制代码代码如下:  lspci命令的输出看上去想这样:复制代码代码如下: 00:00.0 Host bridge: Intel Corporation Haswell-ULT DRAM Controller (rev 09)    00:02.0 VGA compatible controller: Intel Corporation Haswell-ULT Integrated Graphics Controller (rev 09)    00:03.0 Audio device: Intel Corporation Haswell-ULT HD Audio Controller (rev 09)    00:14.0 USB controller: Intel Corporation 8 Series USB xHCI HC (rev 04)    00:16.0 Communication controller: Intel Corporation 8 Series HECI #0 (rev 04)    00:1b.0 Audio device: Intel Corporation 8 Series HD Audio Controller (rev 04)    00:1c.0 PCI bridge: Intel Corporation 8 Series PCI Express Root Port 1 (rev e4)    00:1c.1 PCI bridge: Intel Corporation 8 Series PCI Express Root Port 2 (rev e4)    00:1c.2 PCI bridge: Intel Corporation 8 Series PCI Express Root Port 3 (rev e4)    00:1c.4 PCI bridge: Intel Corporation 8 Series PCI Express Root Port 5 (rev e4)    00:1c.5 PCI bridge: Intel Corporation 8 Series PCI Express Root Port 6 (rev e4)    00:1f.0 ISA bridge: Intel Corporation 8 Series LPC Controller (rev 04)    00:1f.3 SMBus: Intel Corporation 8 Series SMBus Controller (rev 04)    02:00.0 Multimedia controller: Broadcom Corporation Device 1570    03:00.0 Network controller: Broadcom Corporation BCM4360 802.11ac Wireless Network Adapter (rev 03)    04:00.0 SATA controller: Marvell Technology Group Ltd. 88SS9183 PCIe SSD Controller (rev 14)这些命令会同时列出有线和无线的网卡。你应该注意到上面的输出中显示我的系统中没有有线网卡。因为我使用的是Macbook Air,它没有以太网端口
  • Zabbix由浅入深之监控Docker(基础篇)