Let‘s Go Rust 系列之定时器 Ticker Timer

前言

在实际项目开发中,经常会有定时任务的功能开发需求,定时任务主要分为两种,

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: 

你可能感兴趣的:(DEEPNOVA开发者社区,rust,golang,r语言)