ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

React 优先级调度器 Scheduler 的小顶堆原理

React 优先级调度器 Scheduler 的小顶堆原理 React 优先级调度器 Scheduler 的小顶堆原理在 React 18 的并发渲染Concurrent Mode架构中能够做到“高优先级用户交互如输入打字即时插队低优先级后台渲染如长列表 Diff让出时间切片”的幕后功臣正是独立维护的包——scheduler。当我们深入scheduler的源码目录会发现其核心任务队列既不是普通的 JavaScript 原生数组也不是 FIFO先进先出的线性链表而是两颗**小顶堆Min-Heap**二叉树taskQueue和timerQueue。为什么 React 会选择小顶堆它又是如何在单线程的 JavaScript 环境中实现微秒级的高效任务抢占与调度的为什么是小顶堆时间复杂度与动态排序的权衡在并发调度场景下系统中的任务具有动态的到期时间Expiration Time。调度器需要以极高的频率执行两类核心操作获取当前最紧急的任务Peek必须是全局到期时间最早值最小的那个任务弹出已完成任务并插入新任务Pop Push随着用户不断点击新任务随时插入队列。如果使用普通数组每次插入时进行全局sort()排序时间复杂度为 $O(N \log N)$在大并发更新时开销极大如果保持数组有序插入操作需要移动后续元素复杂度为 $O(N)$。而**小顶堆Min Heap**二叉树拥有极致的算法特性查询堆顶最小元素Peek$O(1)$ 常数时间插入新节点Push Sift Up 上浮$O(\log N)$移除堆顶节点Pop Sift Down 下沉$O(\log N)$。在 React 中小顶堆直接用扁平的 JavaScript 一维数组进行高效存储无需创建额外的指针对象对 V8 引擎的垃圾回收极其友好。graph TD Node0[[0] Task(exp: 100ms) - 堆顶最紧急] -- Node1[[1] Task(exp: 150ms)] Node0 -- Node2[[2] Task(exp: 200ms)] Node1 -- Node3[[3] Task(exp: 300ms)] Node1 -- Node4[[4] Task(exp: 180ms)]Scheduler 小顶堆核心算法实现以下是提炼自 React 源码中SchedulerMinHeap.js的完整算法骨架export interface HeapTask { id: number; sortIndex: number; // 比较的键值通常是 expirationTime 或 startTime } export class MinHeapT extends HeapTask { private heap: T[] []; push(node: T) { const index this.heap.length; this.heap.push(node); this.siftUp(node, index); } peek(): T | null { return this.heap.length 0 ? null : this.heap[0]; } pop(): T | null { if (this.heap.length 0) return null; const first this.heap[0]; const last this.heap.pop()!; if (last ! first) { this.heap[0] last; this.siftDown(last, 0); } return first; } private siftUp(node: T, i: number) { let index i; while (index 0) { const parentIndex (index - 1) 1; // 无符号右移计算父节点下标 const parent this.heap[parentIndex]; if (this.compare(parent, node) 0) { // 父节点比当前节点大不满足小顶堆性质交换并继续向上爬升 this.heap[parentIndex] node; this.heap[index] parent; index parentIndex; } else { return; } } } private siftDown(node: T, i: number) { let index i; const length this.heap.length; const halfLength length 1; while (index halfLength) { const leftIndex (index 1) 1; const left this.heap[leftIndex]; const rightIndex leftIndex 1; const right this.heap[rightIndex]; // 寻找左、右子节点中较小的那一个 if (this.compare(left, node) 0) { if (rightIndex length this.compare(right, left) 0) { this.heap[index] right; this.heap[rightIndex] node; index rightIndex; } else { this.heap[index] left; this.heap[leftIndex] node; index leftIndex; } } else if (rightIndex length this.compare(right, node) 0) { this.heap[index] right; this.heap[rightIndex] node; index rightIndex; } else { return; } } } private compare(a: T, b: T): number { const diff a.sortIndex - b.sortIndex; return diff ! 0 ? diff : a.id - b.id; // 到期时间相同时按创建 ID 先来后到 } }双堆协同taskQueue 与 timerQueue 的流转在真实的调度运行中React 维护了两个小顶堆timerQueue延时任务堆存放未到开始时间startTime currentTime的延迟任务按startTime排序taskQueue就绪任务堆存放已经可以立即执行的任务按expirationTime到期时间排序。调度器的核心主循环workLoop逻辑如下在每次循环前调用advanceTimers(currentTime)检查timerQueue堆顶的任务如果某个任务的startTime已经到达当前时间将其从timerQueue弹出并推入taskQueue取出taskQueue堆顶的最紧急任务进行执行如果当前帧默认 5ms时间切片耗尽且当前任务尚未执行完毕Scheduler 挂起执行利用MessageChannel宏任务向主线程重新注册一个调度回调让出主线程给浏览器响应用户输入或渲染绘制。正是依托小顶堆极致的 $O(\log N)$ 运算效率React 才能在每秒成百上千次的复杂并发更新中始终游刃有余地掌控全局节奏。
返回列表