Hello everyone, I'm Casson.
React
most difficult part of understand is the 161de3d3d16754 scheduling algorithm , which is not only abstract and complex, but also reconstructed once.
It can be said that only the React
team can fully understand this algorithm.
In this case, this article tries to talk about the scheduling algorithm React
team members.
Welcome to join human high-quality front-end framework group , with flying
What is a scheduling algorithm
React
before v16 is: when the component tree is very large, the update state may cause the page . The synchronous and uninterrupted 161de3d3d167f2.
To solve this problem, React
proposed the Fiber
architecture, with the intention of changing the update process to an asynchronous, interruptible .
The final interaction process is as follows:
- Different interactions generate updates with different priorities (for example, the update priority in the
onClick
callback is the highest, and the update priority triggered in theuseEffect
- scheduling algorithm choose from a number of updates
priority as this
render
priority - In Step 2 selected
priority tree assembly
render
In render
process, if they trigger interaction process, step 2 and select a higher priority, the previous
render
interrupted to new priority to re-start
render
.
This article is going to talk about the scheduling algorithm in step 2.
expirationTime scheduling algorithm
scheduling algorithm needs to solve is: how to select one of the updated priorities from the many updates as the
render
?
The earliest algorithm is called the expirationTime algorithm.
Specifically, the updated priorities and current time-triggered interaction and priority corresponding delay time Related:
// MAX_SIGNED_31_BIT_INT为最大31 bit Interger
update.expirationTime = MAX_SIGNED_31_BIT_INT - (currentTime + updatePriority);
updatePriority
high-priority update u1 and the low-priority update u2 are 0 and 200 respectively, then
MAX_SIGNED_31_BIT_INT - (currentTime + 0) > MAX_SIGNED_31_BIT_INT - (currentTime + 200)
// 即
u1.expirationTime > u2.expirationTime;
Represents u1
higher priority.
expirationTime algorithm works straightforward: all updates every elected in highest priority .
How to represent "batch"
In addition to this, there is a problem to be solved: how to represent batch ?
What is batch Consider the following example:
// 定义状态num
const [num, updateNum] = useState(0);
// ...某些修改num的地方
// 修改的方式1
updateNum(3);
// 修改的方式2
updateNum(num => num + 1);
Both ways modify the state will create updates, the difference is:
- The first way, without considering the state before the update, directly modify the
num
- The second approach, need to be based pre-update state calculate a new state
Because of the second way, there may be continuity between updates.
Therefore scheduling algorithm calculate a the priority component
render
actual participation calculation value of the current state is:
The priority calculated by + The other priorities related to this priority correspond to update
These interrelated, consecutive updates are called a batch (
batch
).
expirationTime algorithm calculates the batch
updates with a priority greater than a certain value (
priorityOfBatch
) will all be classified as the same batch.
const isUpdateIncludedInBatch = priorityOfUpdate >= priorityOfBatch;
expirationTime algorithm ensures that the
render
asynchronous interruptible, always highest priority update is processed first.
During this period the feature was called Async Mode
.
IO-intensive scenarios
Async Mode
can solve the following problems:
- The logic of the component tree is complex, causing the update to
render
which can be interrupted)
- Important interactions are more responsive (because different interactions generate
updates with
priorities for 161de3d3d16ca7)
These problems are collectively referred to as CPU intensive problems.
On the front end, there is another type of problem that will also affect the experience, that is, waiting for requesting data. This type of problem is called a IO-bound problem.
In order to solve IO-intensive problems,
React
proposed Suspense
. Consider the following code:
const App = () => {
const [count, setCount] = useState(0);
useEffect(() => {
const t = setInterval(() => {
setCount(count => count + 1);
}, 1000);
return () => clearInterval(t);
}, []);
return (
<>
<Suspense fallback={<div>loading...</div>}>
<Sub count={count} />
</Suspense>
<div>count is {count}</div>
</>
);
};
in:
- An update is triggered every second, updating the status
count
tocount => count + 1
Sub
asynchronous request will be initiated in 061de3d3d16da9. Before the request is returned,Sub
withSuspense
will renderfallback
Assuming that the request returns after three seconds, ideally, UI
will be displayed in sequence before and after the request is initiated:
// Sub内请求发起前
<div class=“sub”>I am sub, count is 0</div>
<div>count is 0</div>
// Sub内请求发起第1秒
<div>loading...</div>
<div>count is 1</div>
// Sub内请求发起第2秒
<div>loading...</div>
<div>count is 2</div>
// Sub内请求发起第3秒
<div>loading...</div>
<div>count is 3</div>
// Sub内请求成功后
<div class=“sub”>I am sub, request success, count is 4</div>
<div>count is 4</div>
From the user's perspective, two tasks are executing concurrently:
- The task of requesting
Sub
(observe the changes of thediv
- Change the task of
count
(observe the change of thediv
Suspense
brings the intuitive feeling multitasking concurrently executing
Therefore, Async Mode
(asynchronous mode) was also renamed Concurrent Mode
(concurrent mode).
an unsolvable bug
So, is the update Suspense high or low?
When the request is successful, the reasonable logic should be to display the successful UI as soon as possible. Therefore, the corresponding update of Suspense should be a high-priority update
. So, there are two types of updates in the example:
Suspense
corresponding IO high priority update, referredu0
- Low-quality
CPU
updates generated per second, referred to asu1
,u2
,u3
etc.
Under the expirationTime algorithm:
// u0优先级远大于u1、u2、u3...
u0.expirationTime >> u1.expirationTime > u2.expirationTime > …
u0
highest priority, then u1
and subsequent updates need to wait for
u0
executed before proceeding.
And u0
needs to wait for to complete the request and to execute. Therefore, before and after the request is initiated, UI
will be displayed as:
// Sub内请求发起前
<div class=“sub”>I am sub, count is 0</div>
<div>count is 0</div>
// Sub内请求发起第1秒
<div>loading...</div>
<div>count is 0</div>
// Sub内请求发起第2秒
<div>loading...</div>
<div>count is 0</div>
// Sub内请求发起第3秒
<div>loading...</div>
<div>count is 0</div>
// Sub内请求成功后
<div class=“sub”>I am sub, request success, count is 4</div>
<div>count is 4</div>
From the user's point of view, the second div
was stuck for 3 seconds and then suddenly changed to 4.
Therefore, considering only case of CPU-intensive scenes, high priority update before execution algorithm is no problem.
But considering case of IO-intensive scenes,
high priority IO update will block
low priority CPU update, which is obviously wrong.
So the expirationTime algorithm does not support concurrent updates well.
expirationTime algorithm online Demo
The reason for the bug
biggest problem with the 161de3d3d17117 expirationTime algorithm
expirationTime
field couples the priority and the batch which limits the expressiveness of the model.
This causes the high-quality IO update to not be
into the same batch as the 161de3d3d17143 low-quality CPU update. Then the
low-quality CPU update must wait for the
high-quality IO update to be processed before processing.
If different updates can flexibly divide batches bug
will not be generated.
The refactoring is imminent, and the goal of the refactoring is clear: split priority vs batch into two fields.
Lane scheduling algorithm
The new scheduling algorithm is called Lane
, how does he define priority and batch ?
For priority, a
lane
is a 32bit Interger
, and the highest bit is the sign bit, so there can be up to 31 bits involved in the operation.
Different priorities correspond to different lane
, the lower the bit represents the higher the priority, for example:
// 对应SyncLane,为最高优先级
0b0000000000000000000000000000001
// 对应InputContinuousLane
0b0000000000000000000000000000100
// 对应DefaultLane
0b0000000000000000000000000010000
// 对应IdleLane
0b0100000000000000000000000000000
// 对应OffscreenLane,为最低优先级
0b1000000000000000000000000000000
batch defined by lanes
, a lanes
is also a 32bit Interger
, representing one or more lane sets .
lane
can easily be grouped into the same batch using
bitwise operations:
// 要使用的批次
let lanesForBatch = 0;
const laneA = 0b0000000000000000000000001000000;
const laneB = 0b0000000000000000000000000000001;
// 将laneA纳入批次中
lanesForBatch |= laneA;
// 将laneB纳入批次中
lanesForBatch |= laneB;
Suspense
of the bug
mentioned above is caused by the inability of the expirationTime algorithm to flexibly demarcate the
batch.
lanes
has no such concerns at all. Any priority (lane) as the same 161de3d3d1734c batch can be easily done
bit operations.
Summarize
scheduling algorithm to solve two problems:
- pick priority
- select batch
expirationTime
field used in the expirationTime algorithm couples these two concepts, resulting in inflexibility.
Lane algorithm solves the above problems.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。