新的定时器实现
2018 年 3 月 30 日
大家周五快乐!
为了给美好的一周画上句号,Tokio 发布了新版本。此版本包含一个全新的定时器实现。
定时器
有时(通常),人们希望根据时间执行代码。可能一个函数需要在特定的时刻运行。可能一个读取操作需要限制在固定的持续时间内。为了处理时间,人们需要访问定时器!
一些历史
tokio-timer
crate 已经存在一段时间了。它最初是使用哈希定时轮(pdf 警告)构建的。它的粒度为 100 毫秒,因此任何分辨率小于 100 毫秒的超时设置都会被向上舍入。通常,在基于网络的应用程序的上下文中,这很好。超时通常至少为 30 秒,并且不需要高精度。
但是,在某些情况下,100 毫秒太粗糙了。此外,最初的 Tokio 定时器实现存在许多烦人的错误,并且由于其采取的实现策略,对边缘情况的处理也不是很好。
一个新的开始
定时器已被从头开始重写,并作为 tokio-timer
0.2 发布。在大多数情况下,API 非常相似,但实现方式完全不同。
它没有仅仅使用单个哈希定时轮实现,而是使用了一种分层方法(也在上面链接的论文中描述)。
定时器使用六个独立的层级。每个层级都是一个包含 64 个槽位的哈希轮。最低层级中的槽位代表一毫秒。下一个层级代表 64 毫秒(1 x 64 个槽位),依此类推。因此,每个层级上的一个槽位所覆盖的时间量与下面整个层级相同。
当设置超时时,如果超时时间在当前时刻的 64 毫秒内,则将其放入最低层级。如果超时时间在 64 毫秒到 4,096 毫秒之间,则将其放入第二层级,依此类推。
随着时间的推移,最低层级中的超时将被触发。一旦到达最低层级的末尾,下一个层级中的所有超时都将从该层级中移除并移动到最低层级。
使用这种策略,所有定时器操作(创建超时、取消超时、触发超时)都是恒定的。即使有大量未完成的超时,也能获得非常好的性能。
快速浏览 API。
如上所述,API 实际上没有改变。主要有三种类型
以及一个快速示例
use tokio::prelude::*;
use tokio::timer::Delay;
use std::time::{Duration, Instant};
fn main() {
let when = Instant::now() + Duration::from_millis(100);
let task = Delay::new(when)
.and_then(|_| {
println!("Hello world!");
Ok(())
})
.map_err(|e| panic!("delay errored; err={:?}", e));
tokio::run(task);
}
上面的示例创建了一个新的 Delay
实例,它将在未来 100 毫秒后完成。new
函数接受一个 Instant
,因此我们计算 when
为从现在起 100 毫秒后的时刻。
一旦到达该时刻,Delay
Future 完成,从而导致 and_then
代码块被执行。
此版本附带一个简短的指南,解释如何使用定时器和 API 文档。
集成在运行时中
使用定时器 API 需要运行一个定时器实例。Tokio 运行时 负责处理所有这些设置。
当运行时通过 tokio::run
启动或直接调用 Runtime::new
时,将启动一个线程池。每个工作线程将获得一个定时器实例。因此,这意味着如果运行时启动 4 个工作线程,将有 4 个定时器实例,每个线程一个。这样做允许使用定时器而无需支付同步成本,因为定时器将与使用各种定时器类型(Delay
、Timeout
、Interval
)的代码位于同一线程上。
就这样,祝大家周末愉快!