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

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

  • 在固定的时刻执行某个任务,也就是 Timer
  • 基于固定的时间间隔,周期的执行某个任务,也就是Ticker
很多基于时间的调度任务框架都离不开这两种类型。
本文将会分别介绍在Golang和Rust语言中这两种定时器类型以及它们的使用方法。
Golang
Golang的标准库 time 中就包含了 Ticker 和 Timer 这两种定时器类型,在package中添加引用就可,如下: ?
`import (
"time"

)`
Rust
本文中,对于Rust 将会使用第三方crate crossbeam-channel 提供的定时器类型,因为这个crate中的特性和Golang的特性是非常类似的,两者之间才具有可比性。
  • https://docs.rs/crossbeam/0.8...
  • https://docs.rs/crossbeam/0.8...
因为是第三方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 { 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,/// 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 { 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) -> Result { 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 { 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 { 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: 371585400 } elapsed: 200.3341ms Instant { tv_sec: 355149, tv_nsec: 471585400 } elapsed: 300.3773ms Instant { tv_sec: 355149, tv_nsec: 571585400 } elapsed: 400.2563ms Instant { tv_sec: 355149, tv_nsec: 671585400 } elapsed: 500.379ms

ticker所在线程休眠
crossbeam_channel::tick 所关联的channel的容量是1,如果通道中已经有了message,那么后续新的message过来后就会被丢弃,我们可以用下面样例验证
fn sleep_ticker(){let ms = |ms| Duration::from_millis(ms); // Returns `true` if `a` and `b` are very close `Instant`s. // 如果时间 a 和 b 相差不到 50 milliseconds ,就返回true let eq = |a,b| a+ms(50) > b &&b+ms(50)>a; let start = Instant::now(); // 定时器每隔 100 milliseconds 往r对应的channel中发送一个消息 let r = tick(ms(100)); // This message was sent 100 ms from the start and received 100 ms from the start. // tick开始100 ms 后,收到消息 assert!(eq(r.recv().unwrap(), start + ms(100))); // 的确过去了 100 ms assert!(eq(Instant::now(), start + ms(100))); // 这里 tick 对应的channel里面是空,所以在 [100ms 600ms] 线程休眠的时间内, 第200ms的消息仍然正常写入, 300ms,400ms,500ms,600ms的都无法写进去 thread::sleep(ms(500)); // This message was sent 200 ms from the start and received 600 ms from the start. // tick 开始后的200 ms ,收到消息 // 这里到了第600ms时刻,channel里面有 200ms 的消息 assert!(eq(r.recv().unwrap(), start + ms(200))); assert!(eq(Instant::now(), start + ms(600))); // 这里tick对应的channel又变为空了,所以700ms的消息可以正常写入 // This message was sent 700 ms from the start and received 700 ms from the start. // tick 开始后的700ms,收到消息 assert!(eq(r.recv().unwrap(), start + ms(700))); assert!(eq(Instant::now(), start + ms(700))); }

这个例子中,接受第一个100ms的消息后,当前thread睡眠了500ms,然后从ticker中接受了 Instant(200ms) 的消息,下一次接受的是 Instant(700ms) 的消息。 这个结果咋一看感觉特别诡异,为什么接受了 Instant(200ms) 的消息后,再次收到的就是 Instant(700ms) 的消息呢? 原因在于ticker绑定的channel的容量只有1,所以:
  1. 第100ms 消息到达时,channel是空,往channel中填入成功,然后 r.recv().unwap() 读取消息时候又把channel清空了
  2. 当前thread进入休眠
  3. 在当前thread休眠的期间,所以200ms消息到达时,channel是空,往channel中填入成功,此时channel满了
  4. 随后300ms,400ms,500ms,600ms的消息到达时,channel是满的,消息被丢弃
  5. 600ms后当前thread醒来,执行 r.recv().unwap() 读取消息时候,此时channel中的是200ms的消息,所以读出来是Instant(200ms) ,并把channel清空了
  6. 700ms的消息到达时候,channel是空,往channel中填入成功, r.recv().unwap() 就读取到了 700ms 的消息
参与 select
因为tick 返回的是 Receiver ,所以可以放入到 select 中跟正常 crossbeam_channel 创建的channel一同进行监听,如下
fn select_channl(){let start = Instant::now(); let ticker = tick(Duration::from_millis(100)); let (_s,r) = unbounded::<()>(); // 这里如果写成let (_,r) = unbounded::<()>(); 那么 r 就会一直可读,读取的数据 Err(RecvError)for _ in 0..5 {select! { recv(r)->msg=>{ println!("recve {:?}",msg); }, recv(ticker)->msg=>{ println!("elapsed: {:?} {:?}", msg.unwrap(),start.elapsed()); }, } } } elapsed: Instant { tv_sec: 179577, tv_nsec: 291446700 } 100.4331ms elapsed: Instant { tv_sec: 179577, tv_nsec: 391877000 } 200.8107ms elapsed: Instant { tv_sec: 179577, tv_nsec: 492246700 } 301.2404ms elapsed: Instant { tv_sec: 179577, tv_nsec: 592683200 } 401.4015ms elapsed: Instant { tv_sec: 179577, tv_nsec: 692843600 } 501.5007ms

这里需要注意的是,上面let (_s,r) = unbounded::<()>(); 如果写成 let (_,r) = unbounded::<()>(); 的话, select! 就会一直进入到 recv(r)->msg分支中,读取的消息是 Err(RecvError) ?
具体原因跟 disconnection 有关, let (_,r) = unbounded::<()>(); 相当于Sender 在一开始就被drop掉了。
When all senders or all receivers associated with a channel get dropped, the channel becomes disconnected. No more messages can be sent, but any remaining messages can still be received. Send and receive operations on a disconnected channel never block.
翻译过来就是:
当一个channel所关联的所有sender 或 所有的receiver都被drop掉之后,这个channel就会变成 disconnected. 不可以再往里面发送消息,但是如果channel里面有剩余的消息可以继续接受。对一个 disconnected的channel进行Send或receive操作都不会阻塞。
golang
在golang里面使用 time.NewTicker 来创建一个Ticker 官方描述
NewTicker returns a new Ticker containing a channel that will send the time on the channel after each tick. The period of the ticks is specified by the duration argument. The ticker will adjust the time interval or drop ticks to make up for slow receivers. The duration d must be greater than zero; if not, NewTicker will panic. Stop the ticker to release associated resources.
这里提到ticker可以使用Reset重置间隔,使用Stop来关闭ticker,不过Stop并不会关闭ticker所关联的channel。
Ticker的内部源码实现
// A Ticker holds a channel that delivers ``ticks'' of a clock // at intervals. type Ticker struct { C <-chan Time // The channel on which the ticks are delivered. r runtimeTimer }// NewTicker returns a new Ticker containing a channel that will send // the time on the channel after each tick. The period of the ticks is // specified by the duration argument. The ticker will adjust the time // interval or drop ticks to make up for slow receivers. // The duration d must be greater than zero; if not, NewTicker will // panic. Stop the ticker to release associated resources. func NewTicker(d Duration) *Ticker { if d <= 0 { panic(errors.New("non-positive interval for NewTicker")) } // Give the channel a 1-element time buffer. // If the client falls behind while reading, we drop ticks // on the floor until the client catches up. c := make(chan Time, 1) t := &Ticker{ C: c, r: runtimeTimer{ when:when(d), period: int64(d), f:sendTime, arg:c, }, } startTimer(&t.r) return t }

可以看到golang的Ticker也是关联了一个channel,且这个channel的buffer长度也是1.
快速上手
func simple_ticker() { tick := time.NewTicker(time.Duration(100 * time.Millisecond)) start := time.Now() for i := 0; i < 5; i++ { msg := <-tick.C fmt.Printf("%v elapsed: %v\n", msg, time.Since(start)) } }

执行结果如下:
2021-09-26 11:50:11.3452615 +0800 CST m=+0.100763101 elapsed: 100.807ms 2021-09-26 11:50:11.4447965 +0800 CST m=+0.200297901 elapsed: 200.2688ms 2021-09-26 11:50:11.5453194 +0800 CST m=+0.300820801 elapsed: 300.7901ms 2021-09-26 11:50:11.6448699 +0800 CST m=+0.400371301 elapsed: 400.3422ms 2021-09-26 11:50:11.7452991 +0800 CST m=+0.500800501 elapsed: 500.7743ms

ticker所在线程休眠
golang版本的Ticker和 crossbeam_channel::tick 类似,也是关联了一个buffer长度是1的channel,所以执行的逻辑也和 crossbeam_channel::tick 的一致,这里就不多过解释。
func ms(d int) time.Duration { return time.Duration(d * int(time.Millisecond)) } func eq(a, b time.Time) bool { return a.Add(ms(50)).After(b) && b.Add(ms(50)).After(a) }func assert(a, b time.Time) { if !eq(a, b) { panic(a) } }func sleep_ticker() {start := time.Now()// 定时器每隔 100 milliseconds 往r对应的channel中发送一个消息 r := time.NewTicker(ms(100)) defer r.Stop()// This message was sent 100 ms from the start and received 100 ms from the start. // tick开始100 ms 后,收到消息 msg := <-r.C assert(msg, start.Add(ms(100))) // 的确过去了 100 ms assert(time.Now(), start.Add(ms(100)))// 这里 tick 对应的channel里面是空,所以在 [100ms 600ms] 线程休眠的时间内, 第200ms的消息仍然正常写入, 300ms,400ms,500ms,600ms的都无法写进去 time.Sleep(ms(500))// This message was sent 200 ms from the start and received 600 ms from the start. // tick 开始后的200 ms ,收到消息 // 这里到了第600ms时刻,channel里面有 200ms 的消息 msg = <-r.C assert(msg, start.Add(ms(200))) assert(time.Now(), start.Add(ms(600)))// 这里tick对应的channel又变为空了,所以700ms的消息可以正常写入 // This message was sent 700 ms from the start and received 700 ms from the start. // tick 开始后的700ms,收到消息 msg = <-r.C assert(msg, start.Add(ms(700))) assert(time.Now(), start.Add(ms(700)))}

参与select
由于Ticker的成员C本身就是一个channel
type Ticker struct { C <-chan Time // The channel on which the ticks are delivered. r runtimeTimer }

所以可以把Ticker.C 加入到select中,如下:
func select_ticker() { tick := time.NewTicker(time.Duration(100 * time.Millisecond)) defer tick.Stop() start := time.Now()r := make(chan int) for i := 0; i < 5; i++ { select { case msg := <-tick.C: fmt.Printf("%v elapsed: %v\n", msg, time.Since(start)) case <-r: fmt.Println("recv from r") }} }

输出
2021-09-24 14:57:23.7813998 +0800 CST m=+0.100670501 elapsed: 100.6697ms 2021-09-24 14:57:23.8818368 +0800 CST m=+0.201107601 elapsed: 201.0681ms 2021-09-24 14:57:23.9814607 +0800 CST m=+0.300731501 elapsed: 300.7018ms 2021-09-24 14:57:24.0810694 +0800 CST m=+0.400340201 elapsed: 400.3102ms 2021-09-24 14:57:24.1815779 +0800 CST m=+0.500848601 elapsed: 500.8122ms

Timer
Rust
官网描述
Creates a receiver that delivers a message after a certain duration of time.
引用
The channel is bounded with capacity of 1 and never gets disconnected. Exactly one message will be sent into the channel after duration elapses. The message is the instant at which it is sent.
翻译过来就是
创建一个channel的receiver,经过特定时间间隔后这个channel中会塞入消息
这个channel的容量是1,永远不会关闭。 每隔固定间隔发送消息到channel中,消息的值就是代表消息发送时刻的Instant。
看下after的源码如下,同样可以看到after返回的也是是一个channel的Receiver
pub fn after(duration: Duration) -> Receiver { Receiver { flavor: ReceiverFlavor::At(Arc::new(flavors::at::Channel::new_timeout(duration))), } }

进入到 flavors::at::Channel::new_timeout
/// Channel that delivers a message at a certain moment in time pub(crate) struct Channel { /// The instant at which the message will be delivered. delivery_time: Instant,/// `true` if the message has been received. received: AtomicBool, }impl Channel { /// Creates a channel that delivers a message at a certain instant in time. #[inline] pub(crate) fn new_deadline(when: Instant) -> Self { Channel { delivery_time: when, received: AtomicBool::new(false), } } /// Creates a channel that delivers a message after a certain duration of time. #[inline] pub(crate) fn new_timeout(dur: Duration) -> Self { Self::new_deadline(Instant::now() + dur) }/// Attempts to receive a message without blocking. #[inline] pub(crate) fn try_recv(&self) -> Result { // We use relaxed ordering because this is just an optional optimistic check. if self.received.load(Ordering::Relaxed) { // The message has already been received. return Err(TryRecvError::Empty); }if Instant::now() < self.delivery_time { // The message was not delivered yet. return Err(TryRecvError::Empty); }// Try receiving the message if it is still available. if !self.received.swap(true, Ordering::SeqCst) { // Success! Return delivery time as the message. Ok(self.delivery_time) } else { // The message was already received. Err(TryRecvError::Empty) } }/// Receives a message from the channel. #[inline] pub(crate) fn recv(&self, deadline: Option) -> Result { // We use relaxed ordering because this is just an optional optimistic check. if self.received.load(Ordering::Relaxed) { // The message has already been received. utils::sleep_until(deadline); return Err(RecvTimeoutError::Timeout); }// Wait until the message is received or the deadline is reached. loop { let now = Instant::now(); let deadline = match deadline { // Check if we can receive the next message. _ if now >= self.delivery_time => break, // Check if the timeout deadline has been reached. Some(d) if now >= d => return Err(RecvTimeoutError::Timeout),// Sleep until one of the above happens Some(d) if d < self.delivery_time => d, _ => self.delivery_time, }; thread::sleep(deadline - now); }// Try receiving the message if it is still available. if !self.received.swap(true, Ordering::SeqCst) { // Success! Return the message, which is the instant at which it was delivered. Ok(self.delivery_time) } else { // The message was already received. Block forever. utils::sleep_until(None); unreachable!() } }/// Reads a message from the channel. #[inline] pub(crate) unsafe fn read(&self, token: &mut Token) -> Result { token.at.ok_or(()) }/// Returns `true` if the channel is empty. #[inline] pub(crate) fn is_empty(&self) -> bool { // We use relaxed ordering because this is just an optional optimistic check. if self.received.load(Ordering::Relaxed) { return true; }// If the delivery time hasn't been reached yet, the channel is empty. if Instant::now() < self.delivery_time { return true; }// The delivery time has been reached. The channel is empty only if the message has already // been received. self.received.load(Ordering::SeqCst) }/// 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 { Some(1) } }

和tick一样,方法 capacity中返回的是 Some(1) ,表明 after 返回的Receiver所关联的chnnel的容量的确也是1。
快速上手
fn simple_after() { let start = Instant::now(); let af = after(Duration::from_millis(100)); for _ in 0..5 { af.recv().unwrap(); println!("elapsed: {:?}", start.elapsed()); }}

上面样例中,在100ms的时刻会往af关联channel写入消息,之后就不会再有新的消息到达af关联channel,所以af.recv() 在收到100ms 的消息后,后面就会永远阻塞,输出如下:
`elapsed: 100.1125ms
^C`
after所在线程休眠
fn sleep_after(){ // Converts a number of milliseconds into a `Duration`. let ms = |ms| Duration::from_millis(ms); // Returns `true` if `a` and `b` are very close `Instant`s. let eq = |a, b| a + ms(50) > b && b + ms(50) > a; let start = Instant::now(); let r = after(ms(100)); thread::sleep(ms(500)); // This message was sent 100 ms from the start and received 500 ms from the start. assert!(eq(r.recv().unwrap(), start + ms(100))); assert!(eq(Instant::now(), start + ms(500))); }

由于当前thread休眠的时候,r关联的channel是空的,所以当100ms的消息到来的时候可以成功写入到r关联的channel中,然后等当前thread醒来后,执行 r.recv().unwrap() 就可以拿到100ms的消息。 ?
参与select
与 tick同理,after也可以参与到select中
fn select_after() {let start = Instant::now(); let (_s, r) = unbounded::(); let timeout = Duration::from_millis(100); select! { recv(r) -> msg => println!("received {:?}", msg), recv(after(timeout)) -> msg => println!("timed out {:?} {:?}",msg.unwrap(),start.elapsed()), }}

输出
timed out Instant { tv_sec: 181366, tv_nsec: 193851700 } 100.1291ms

at
at和after比较类似,at是指定具体的时间点,官网描述如下:
Creates a receiver that delivers a message at a certain instant in time.
引用
The channel is bounded with capacity of 1 and never gets disconnected. Exactly one message will be sent into the channel at the moment in time when. The message is the instant at which it is sent, which is the same as when. If when is in the past, the message will be delivered instantly to the receiver.
需要注意的是,如果指定的时间点比当前时间还要早,那么会立刻发送消息到channel ?
golang
使用Timer官方描述
// NewTimer creates a new Timer that will send // the current time on its channel after at least duration d.

结构体定义
type Timer struct { C <-chan Time // contains filtered or unexported fields }// NewTimer creates a new Timer that will send // the current time on its channel after at least duration d. func NewTimer(d Duration) *Timer { c := make(chan Time, 1) t := &Timer{ C: c, r: runtimeTimer{ when: when(d), f:sendTime, arg:c, }, } startTimer(&t.r) return t }

也是关联一个缓存长度是1的channel。
快速上手
func simple_timer() { tm := time.NewTimer(time.Duration(100 * time.Millisecond)) defer tm.Stop() start := time.Now() // 接受一次后就永远阻塞了,程序报死锁错误 for i := 0; i < 2; i++ { msg := <-tm.C fmt.Printf("%v elapsed: %v\n", msg, time.Since(start)) } }

上面样例中,在100ms的时刻会往 tm.C 写入消息,之后就不会再有新的消息到达 tm.C,所以 msg:=<-tm.C 在收到100ms 的消息后,后面就会永远阻塞,输出如下:
2021-09-24 15:08:56.8384098 +0800 CST m=+0.100381801 elapsed: 100.3695ms fatal error: all goroutines are asleep - deadlock!

所在线程休眠
func sleep_timer() {start := time.Now()// 100 milliseconds 后 往r对应的channel中发送一个消息 r := time.NewTimer(ms(100)) defer r.Stop()//在休眠过程中的100ms的时候,往r.C中写入了100ms的 time.Sleep(ms(500))// 此时里面的是 100ms 的消息 msg := <-r.C assert(msg, start.Add(ms(100))) // 的确过去了 500 ms assert(time.Now(), start.Add(ms(500)))// 到这里就永远阻塞了 msg = <-r.C assert(msg, start.Add(ms(200))) assert(time.Now(), start.Add(ms(600)))}

当前thread休眠的时候,r.C 是空的,所以当100ms的消息到来的时候可以成功写入到 r.C 中,然后等当前thread醒来后,执行 msg := <-r.C 就可以拿到100ms的消息在进程输出。后面由于不会再有新的消息写入到r.C 中,执行 msg := <-r.C 就永远阻塞了。
fatal error: all goroutines are asleep - deadlock!

参与 select
与 time.Ticker 同理,可以把Timer.C 加入到select中. 下面代码中由于tm.C只会收到一次消息,所以for循环中在第二次时就永远阻塞了。
func select_timer() { tm := time.NewTimer(time.Duration(100 * time.Millisecond)) defer tm.Stop() start := time.Now()r := make(chan int)for i := 0; i < 2; i++ { select { case msg := <-tm.C: fmt.Printf("%v elapsed: %v\n", msg, time.Since(start)) case <-r: fmt.Println("recv from r") }} }

输出
2021-09-24 15:13:09.9339061 +0800 CST m=+0.100970401 elapsed: 100.9088ms fatal error: all goroutines are asleep - deadlock!

Instant
instant在Rust和golang中用来度量时间,它们的区别取下:
Rust
目前有两种方法可以获取当前时间 Instant::now 和 SystemTime::now.
  • Instant: 返回的是monotonic clock, 主要用于在计算时间间隔时候有用。
  • SystemTime: 返回的是 wall clock,主要用于跟文件系统或其它进程之间进行沟通使用。
    ?
在Rust中,这两个类型的打印结果对人类来说都是不直观的,也没有提供响应的格式化打印的方法。
golang
Time
time.Now() 返回的Time类型包含 monotonic clock 。
  1. 如果 Time t 包含 monotonic clock,那么t.Add会同时把duration加到 wall clock和 monotonic clock 上。
  2. 由于 t.AddDate(y,m,d) ,t.Round(d) ,t.Truncate(d) 是 wall clock的计算,所以这些方法计算后的结果会去掉monotonic clock.
  3. 如果 Time t ,Time u 都包含 monotonic clock, 那么 t.After(u), t.Before(u), t.Equal(u), and t.Sub(u) 在计算的时候只会使用 monotonic clock. 否则如果u 或 t 任意一个不包含 monotonic clock, 就使用 wall clock来计算。
  4. t.GobEncode, t.MarshalBinary, t.MarshalJSON, and t.MarshalText 会忽略掉 monotonic clock。
  5. == 在计算的时候,不仅仅会比较 instant,还会比较 Location 和 monotonic clock.
  6. 程序在使用Time的时候应该传值而不是指针。也就是说 time 变量或结构体成员应该是类型 time.Time 而不是 *time.Time
  7. Time 值类型的变量可以被多个goroutine安全的并发访问,除了 GobDecode, UnmarshalBinary, UnmarshalJSON and UnmarshalTex
  8. Time instants 可以使用 Before, After,和 Equal 方法进行比较。
  9. Each Time has associated with it a Location, consulted when computing the presentation form of the time, such as in the Format, Hour, and Year methods. The methods Local, UTC, and In return a Time with a specific location. Changing the location in this way changes only the presentation; it does not change the instant in time being denoted and therefore does not affect the computations described in earlier paragraphs.
【Let's Go Rust 系列之定时器 Ticker Timer】Let's Go Rust 系列之定时器 Ticker Timer
文章图片

    推荐阅读