preface
In actual project development, there are often functional development requirements for scheduled tasks. Scheduled tasks are mainly divided into two types,
- Execute a task at a fixed time, that is, Timer
- Based on a fixed time interval, a task is executed periodically, that is, Ticker
Many time-based scheduling frameworks are inseparable from these two types.
This article will introduce these two timer types and their usage in Golang and Rust languages respectively.
Golang
Golang's standard library time Included in Ticker and Timer The two timer types can be referenced in the package as follows:
`import (
"time"
)`
Rust
In this article, a third-party crite will be used for Rust crossbeam-channel The provided timer type is comparable because the features in this crite are very similar to those in Golang.
Because it is a third-party create, add the following in Cargo.toml
crossbeam = "0.8" crossbeam-channel = "0.5"
In addition, add the following reference to the code
use std::time::{Duration, Instant}; use crossbeam::select; use crossbeam_channel::tick; use crossbeam_channel::after; use crossbeam_channel::unbounded; use std::thread;
Next, this article will introduce and compare how to create and use Ticker in Rust and Golang based on different functional use cases.
Ticker
First, it introduces how to create and use Ticker in Rust and Golang
Rust
CRAT crossbeam in Rust_ Used in channel crossbeam_channel::tick Create Ticker
crossbeam_channel::tick Official description
/// 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.
Translated as:
Returns the receiver of a channel, which will deliver messages periodically.
The capacity of this channel is 1 and will never be closed.
Send messages to the channel every fixed time interval. The value of the message is the instant of the message sending time.
Look at the source code of tick as follows. You can see that tick returns a channel Receiver
pub fn tick(duration: Duration) -> Receiver<Instant> { Receiver { flavor: ReceiverFlavor::Tick(Arc::new(flavors::tick::Channel::new(duration))), } }
Then go to flavors::tick::Channel::new and see the definitions and methods of flavors::tick::Channel as follows
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) } }
Note that Some(1) is returned in the above capacity, which verifies the statement that the capacity is 1. This capacity is the key feature of 1, which will be discussed in the following examples.
Get started quickly
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()); } }
In this example, a ticker with an interval of 100ms is created. A message can be obtained from the ticker every 100ms, and the output is as follows
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
The thread where ticker is sleeping
crossbeam_ The capacity of the channel associated with channel:: tick is 1. If there are messages in the channel, subsequent new messages will be discarded. We can use the following example to verify
fn sleep_ticker(){ let ms = |ms| Duration::from_millis(ms); // Returns `true` if `a` and `b` are very close `Instant`s. // Returns true if the difference between times a and b is less than 50 milliseconds let eq = |a,b| a+ms(50) > b && b+ms(50)>a; let start = Instant::now(); // The timer sends a message to the channel corresponding to r every 100 milliseconds let r = tick(ms(100)); // This message was sent 100 ms from the start and received 100 ms from the start. // 100 ms after the start of tick, a message is received assert!(eq(r.recv().unwrap(), start + ms(100))); // Indeed, 100 ms has passed assert!(eq(Instant::now(), start + ms(100))); // Here, the channel corresponding to the tick is empty, so during the [100ms, 600ms] thread sleep time, the 200ms message is still written normally, and the 300ms, 400ms, 500ms and 600ms messages cannot be written thread::sleep(ms(500)); // This message was sent 200 ms from the start and received 600 ms from the start. // 200 ms after the start of tick, a message is received // At the 600ms moment, there is a 200ms message in the channel assert!(eq(r.recv().unwrap(), start + ms(200))); assert!(eq(Instant::now(), start + ms(600))); // Here, the channel corresponding to the tick becomes empty again, so 700ms messages can be written normally // This message was sent 700 ms from the start and received 700 ms from the start. // 700ms after the start of tick, a message is received assert!(eq(r.recv().unwrap(), start + ms(700))); assert!(eq(Instant::now(), start + ms(700))); }
In this example, after receiving the first 100ms message, the current thread sleeps for 500ms, then receives the Instant(200ms) message from the ticker, and the next time accepts the Instant(700ms) message. At first glance, this result feels very strange. Why do you receive Instant(700ms) messages again after receiving Instant(200ms) messages? The reason is that the capacity of the channel bound by ticker is only 1, so:
- When the 100ms message arrives, the channel is empty. The channel is filled successfully, and then r.recv().unwap() empties the channel when reading the message
- The current thread enters sleep
- During the sleep period of the current thread, when the 200ms message arrives, the channel is empty. The channel is filled successfully. At this time, the channel is full
- Then, when the messages of 300ms, 400ms, 500ms and 600ms arrive, the channel is full and the messages are discarded
- After 600ms, the current thread wakes up and executes r.recv().unwap() to read the message. At this time, the message in the channel is 200ms, so it reads Instant(200ms) and clears the channel
- When the 700ms message arrives, the channel is empty. After filling the channel successfully, r.recv().unwap() reads the 700ms message
Participate in select
Because the tick returns the Receiver, it can be put into the select with the normal crossbeam_channel listen together with the channel created by channel, as follows
fn select_channl(){ let start = Instant::now(); let ticker = tick(Duration::from_millis(100)); let (_s,r) = unbounded::<()>(); // Here, let (, R) = unbounded:: < () > (); Then r will always be readable, and the read data err (recvrror) 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
Note that let (_, s, R) = unbounded:: < () > (); If it is written as let (, R) = unbounded:: < () > (); If so, select! It will go all the way to the recv (R) - > MSG branch, and the read message is err (recvrror)
The specific reasons are as follows disconnection For, let (, R) = unbounded:: < () > (); This is equivalent to the Sender being drop ped at the beginning.
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.
Translated as:
When all sender s or receive rs associated with a channel are drop ped, the channel will become disconnected. Messages can no longer be sent, but if there are remaining messages in the channel, they can continue to be accepted. Send ing or receiving a disconnected channel will not block.
golang
Use in golang time.NewTicker To create an official Ticker description
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.
It is mentioned here that the ticker can use Reset to Reset the interval and Stop to close the ticker, but Stop will not close the channel associated with the ticker.
Implementation of Ticker's internal source code
// 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 }
It can be seen that the Ticker of golang is also associated with a channel, and the buffer length of this channel is also 1
Get started quickly
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)) } }
The results are as follows:
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
The thread where ticker is sleeping
Ticker and crossbeam in golang version_ Similar to channel:: tick, it is also associated with a channel with buffer length of 1, so the executed logic is also the same as crossbeam_ The consistency of channel:: tick is not explained here.
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() // The timer sends a message to the channel corresponding to r every 100 milliseconds r := time.NewTicker(ms(100)) defer r.Stop() // This message was sent 100 ms from the start and received 100 ms from the start. // 100 ms after the start of tick, a message is received msg := <-r.C assert(msg, start.Add(ms(100))) // Indeed, 100 ms has passed assert(time.Now(), start.Add(ms(100))) // Here, the channel corresponding to the tick is empty, so during the [100ms, 600ms] thread sleep time, the 200ms message is still written normally, and the 300ms, 400ms, 500ms and 600ms messages cannot be written time.Sleep(ms(500)) // This message was sent 200 ms from the start and received 600 ms from the start. // 200 ms after the start of tick, a message is received // At the 600ms moment, there is a 200ms message in the channel msg = <-r.C assert(msg, start.Add(ms(200))) assert(time.Now(), start.Add(ms(600))) // Here, the channel corresponding to the tick becomes empty again, so 700ms messages can be written normally // This message was sent 700 ms from the start and received 700 ms from the start. // 700ms after the start of tick, a message is received msg = <-r.C assert(msg, start.Add(ms(700))) assert(time.Now(), start.Add(ms(700))) }
Participate in select
Because Ticker member C itself is a channel
type Ticker struct { C <-chan Time // The channel on which the ticks are delivered. r runtimeTimer }
So you can add Ticker.C to the select, as follows:
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") } } }
output
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.
quote
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.
Translated as
Create a receiver of a channel. After a specific time interval, messages will be stuffed into the channel
The capacity of this channel is 1 and will never be closed. Send messages to the channel at regular intervals. The value of the message is the Instant representing the message sending time.
Look at the source code of after as follows. You can also see that after returns a channel Receiver
pub fn after(duration: Duration) -> Receiver<Instant> { Receiver { flavor: ReceiverFlavor::At(Arc::new(flavors::at::Channel::new_timeout(duration))), } }
Enter 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<Instant, TryRecvError> { // 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<Instant>) -> Result<Instant, RecvTimeoutError> { // 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<Instant, ()> { 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<usize> { Some(1) } }
Like tick, Some(1) is returned in the method capacity, indicating that the capacity of the chnnel associated with the Receiver returned by after is indeed 1.
Get started quickly
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()); } }
In the above example, a message will be written to the AF associated channel at the time of 100ms, and then no new message will arrive at the AF associated channel. Therefore, af.recv() will always block after receiving the message of 100ms, and the output is as follows:
`elapsed: 100.1125ms
^C`
after thread sleep
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))); }
When the current thread is sleeping, the channel associated with R is empty. Therefore, when a 100ms message arrives, it can be successfully written to the channel associated with R. after the current thread wakes up, execute r.recv().unwrap() to get a 100ms message.
Participate in select
Similarly to tick, after can also participate in select
fn select_after() { let start = Instant::now(); let (_s, r) = unbounded::<i32>(); let timeout = Duration::from_millis(100); select! { recv(r) -> msg => println!("received {:?}", msg), recv(after(timeout)) -> msg => println!("timed out {:?} {:?}",msg.unwrap(),start.elapsed()), } }
output
timed out Instant { tv_sec: 181366, tv_nsec: 193851700 } 100.1291ms
at
at is similar to after. at specifies a specific time point, Official website description As follows:
Creates a receiver that delivers a message at a certain instant in time.
quote
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.
It should be noted that if the specified time point is earlier than the current time, a message will be sent to the channel immediately
golang
use Timer Official description
// NewTimer creates a new Timer that will send // the current time on its channel after at least duration d.
Structure definition
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 }
It is also associated with a channel with a cache length of 1.
Get started quickly
func simple_timer() { tm := time.NewTimer(time.Duration(100 * time.Millisecond)) defer tm.Stop() start := time.Now() // Once accepted, it will be blocked forever, and the program reports a deadlock error for i := 0; i < 2; i++ { msg := <-tm.C fmt.Printf("%v elapsed: %v\n", msg, time.Since(start)) } }
In the above example, a message will be written to tm.C at 100ms, and no new message will arrive at tm.C. Therefore, MSG: = < - tm.C will always block after receiving a 100ms message. The output is as follows:
2021-09-24 15:08:56.8384098 +0800 CST m=+0.100381801 elapsed: 100.3695ms fatal error: all goroutines are asleep - deadlock!
Thread sleep
func sleep_timer() { start := time.Now() // After 100 milliseconds, send a message to the channel corresponding to r r := time.NewTimer(ms(100)) defer r.Stop() //At 100ms during sleep, 100ms is written to r.C time.Sleep(ms(500)) // At this time, the message inside is 100ms msg := <-r.C assert(msg, start.Add(ms(100))) // Indeed, 500 ms has passed assert(time.Now(), start.Add(ms(500))) // It'll be blocked forever msg = <-r.C assert(msg, start.Add(ms(200))) assert(time.Now(), start.Add(ms(600))) }
When the current thread is dormant, r.C is empty, so when the 100ms message arrives, it can be successfully written to r.C. then when the current thread wakes up, execute MSG: = < - r.C, and you can get the 100ms message for process output. Since no new messages will be written to r.C later, the execution of MSG: = < - r.C will be blocked forever.
fatal error: all goroutines are asleep - deadlock!
Participate in select
Similarly to time.Ticker, Timer.C can be added to select. In the following code, tm.C will only receive a message once, so the for loop will always be blocked the second time.
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") } } }
output
2021-09-24 15:13:09.9339061 +0800 CST m=+0.100970401 elapsed: 100.9088ms fatal error: all goroutines are asleep - deadlock!
Instant
instant is used to measure time in Rust and golang. Their differences are as follows:
Rust
There are two ways to get the current time Instant::now and SystemTime::now.
- Instant: the returned is a monotonic clock, which is mainly used to calculate the time interval.
- SystemTime: returns wall clock, which is mainly used to communicate with the file system or other processes.
In Rust, these two types of printing results are not intuitive to humans, and there is no response format printing method.
golang
Time
The Time type returned by time.Now() contains monotonic clock.
- If Time t contains mononic clock, t.Add will add duration to wall clock and mononic clock at the same time.
- Since t.adddate (y, m, d), t.round (d) and t.truncate (d) are wall clock calculations, the results calculated by these methods will remove the monotonic clock
- If both time t and time u contain mononic clock, t.After(u), t.Before(u), t.Equal(u), and t.Sub(u) will only use mononic clock when calculating. Otherwise, if either u or t does not contain mononic clock, wall clock will be used for calculation.
- t.GobEncode, t.MarshalBinary, t.MarshalJSON, and t.MarshalText ignore the monotonic clock.
- ==During calculation, not only instant, but also Location and monotonic clock will be compared
- When using Time, the program should pass a value instead of a pointer. That is, the Time variable or struct member should be of type time.Time instead of * time.Time
- Variables of Time value type can be accessed safely and concurrently by multiple goroutine s, except GobDecode, UnmarshalBinary, UnmarshalJSON and UnmarshalTex
- Time instances can be compared using the Before, After, and Equal methods.
- 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.