ToC
前言
在 Pingora
的源码阅读中,我不止一次看到有这样的用法:
于是正好借着这个机会,来看看 tokio::select!
的一些知识(常识)。
公平性(Fairness
)
我们知道,Rust
的异步运行时是需要通过 poll
的方式获取每一个 Future
的实际运行状况的。而对于 select!
这种需要「同时」执行多个 FutureBuilder
并同时 poll
的实现,就需要考虑每个分支的执行顺序问题了。
我们以 JavaScript 举个不严谨的例子。假设有以下两个 async function
:
第一个 first
是立即返回的实质同步函数,对应了 Rust
中 poll
直接返回了 Poll::Ready
的情况;而第二个 second
则是等待一秒才会返回。如果我们这样实现 select
:
由于 first
的实现每次都是立即返回,所以这个 select
的实现永远都不会返回 second
的结果。
例子中我们给出的 first
实现是立即返回,但对于实际的实现而言,这个条件可能更宽松。只要排在 select
前面的函数较快返回的概率比排在后面的函数更大,后面的函数返回结果的几率就会显著小于排在前面的函数,造成一定程度的不公平性。而如果是像我们例子里这种立即返回,那么排在后面的函数就永远没有出头之日,造成某种程度的「饿死」。
因此,tokio::select!
的实现对于位于不同「顺位」的 Future
采取了随机选择的方式,以尽量减少这类情况的出现。
biased 是什么?
随机固然保证了一定程度的公平,但其本身就存在一定的开销。此外,对于某些场景,可能也会希望「顺序执行」的稳定性。biased
就是为了这种情景诞生的。还是回到 Pingora
的这个例子:
如果我们希望在处理 shutdown
之前,把已经解析完的所有 request
都通过 read_request
读取出来,此时再使用随机的策略就不大合适了。在这种情况下,我们可以在 select!
的最开头加上 biased;
,这样 tokio::select!
就会保证选择的顺序执行,也就能实现我们想要的效果了。