前言
在实际项目开发中,经常会有定时任务的功能开发需求,定时任务主要分为两种,
1,在固定的时刻执行某个任务,也就是 Timer
2,基于固定的时间间隔,周期的执行某个任务,也就是Ticker
很多基于时间的调度任务框架都离不开这两种类型。
本文将会分别介绍在Golang和Rust语言中这两种定时器类型以及它们的使用方法。
Golang
Golang的标准库 time 中就包含了 Ticker 和 Timer 这两种定时器类型,在package中添加引用就可,如下:
import (
"time"
)
Rust
本文中,对于Rust 将会使用第三方crate crossbeam-channel 提供的定时器类型,因为这个crate中的特性和Golang的特性是非常类似的,两者之间才具有可比性。
https://docs.rs/crossbeam/0.8.1/crossbeam/channel/fn.tick.html
https://docs.rs/crossbeam/0.8.1/crossbeam/channel/fn.after.html
因为是第三方create,所以在Cargo.toml中添加下面内容
crossbeam = "0.8"
crossbeam-channel = "0.5"
另外在代码中添加如下引用
use std::time::{
Duration, Instant};
use crossbeam::select;
use crossbeam_channel::tick;
use crossbeam_channel::after;
use crossbeam_channel::unbounded;
use std::thread;
接下来,本文会基于不同的功能用例分别介绍在Rust和Golang中如何创建和使用Ticker,并进行对比。
Ticker
首先介绍Rust和Golang中如何创建和使用Ticker
Rust
在Rust的crat crossbeam_channel中使用 crossbeam_channel::tick 创建 Ticker
crossbeam_channel::tick
官方描述
/// Creates a receiver that delivers messages periodically.
///
/// The channel is bounded with capacity of 1 and never gets disconnected. Messages will be
/// sent into the channel in intervals of `duration`. Each message is the instant at which it is
/// sent.
翻译过来就是:
返回一个channel的receiver,这个channel会周期性的传递出来消息。
这个channel的容量是1,永远不会关闭。
每隔固定时间间隔发送消息到channel中,消息的值就是消息发送时刻的instant。
看下tick的源码如下,可以看到tick返回的是一个channel的Receiver
pub fn tick(duration: Duration) -> Receiver<Instant> {
Receiver {
flavor: ReceiverFlavor::Tick(Arc::new(flavors::tick::Channel::new(duration))),
}
}
再进入到 flavors::tick::Channel::new 中看到 flavors::tick::Channel 的定义和方法如下
pub(crate) struct Channel {
/// The instant at which the next message will be delivered.
delivery_time: AtomicCell<Instant>,
/// The time interval in which messages get delivered.
duration: Duration,
}
impl Channel {
/// Creates a channel that delivers messages periodically.
#[inline]
pub(crate) fn new(dur: Duration) -> Self {
Channel {
delivery_time: AtomicCell::new(Instant::now() + dur),
duration: dur,
}
}
/// Attempts to receive a message without blocking.
#[inline]
pub(crate) fn try_recv(&self) -> Result<Instant, TryRecvError> {
loop {
let now = Instant::now();
let delivery_time = self.delivery_time.load();
if now < delivery_time {
return Err(TryRecvError::Empty);
}
if self
.delivery_time
.compare_exchange(delivery_time, now + self.duration)
.is_ok()
{
return Ok(delivery_time);
}
}
}
/// Receives a message from the channel.
#[inline]
pub(crate) fn recv(&self, deadline: Option<Instant>) -> Result<Instant, RecvTimeoutError> {
loop {
let delivery_time = self.delivery_time.load();
let now = Instant::now();
if let Some(d) = deadline {
if d < delivery_time {
if now < d {
thread::sleep(d - now);
}
return Err(RecvTimeoutError::Timeout);
}
}
if self
.delivery_time
.compare_exchange(delivery_time, delivery_time.max(now) + self.duration)
.is_ok()
{
if now < delivery_time {
thread::sleep(delivery_time - now);
}
return Ok(delivery_time);
}
}
}
/// Reads a message from the channel.
#[inline]
pub(crate) unsafe fn read(&self, token: &mut Token) -> Result<Instant, ()> {
token.tick.ok_or(())
}
/// Returns `true` if the channel is empty.
#[inline]
pub(crate) fn is_empty(&self) -> bool {
Instant::now() < self.delivery_time.load()
}
/// Returns `true` if the channel is full.
#[inline]
pub(crate) fn is_full(&self) -> bool {
!self.is_empty()
}
/// Returns the number of messages in the channel.
#[inline]
pub(crate) fn len(&self) -> usize {
if self.is_empty() {
0
} else {
1
}
}
/// Returns the capacity of the channel.
#[allow(clippy::unnecessary_wraps)] // This is intentional.
#[inline]
pub(crate) fn capacity(&self) -> Option<usize> {
Some(1)
}
}
注意上面 capacity中返回的是 Some(1) ,验证了容量是1的说法。这个容量是1的特性比较关键,在后面样例中会讲到。
快速上手
fn simple_ticker() {
let start = Instant::now();
let ticker = tick(Duration::from_millis(100));
for _ in 0..5 {
let msg = ticker.recv().unwrap();
println!("{:?} elapsed: {:?}",msg, start.elapsed());
}
}
这个例子里面创建了一个间隔是100ms的ticker,每隔100ms就可以从ticker中获取一个message,输出如下
Instant {
tv_sec: 355149, tv_nsec: 271585400 } elapsed: 100.0824ms
Instant {
tv_sec: 355149, tv_nsec: