Go
The language's standard library provides two types of timersTimer
andTicker
。Timer
After designatedduration
It is triggered after time, go to your own timechannel
Send the current time, after whichTimer
No more time counts.Ticker
Every otherduration
Time will send the current time point to your timechannel
, using the timerchannel
Many timing-related functions can be implemented.
The article mainly involves the following content:
-
Timer
andTicker
The internal structure of the timer -
Timer
andTicker
How to use and precautions - How to be correct
Reset
Timer
Internal representation of the timer
Both timers are based onGo
Runtime timer for languageRealized,
The structure of the following is expressed:
type timer struct { pp puintptr when int64 period int64 f func(interface{}, uintptr) arg interface{} seq uintptr nextwhen int64 status uint32 }
The meaning of the field in the structure is
- when — the time when the current timer is awakened;
- period — the interval between two awakenings;
- f — a function that is called whenever the timer is awakened;
- arg — The parameter passed in by f is called when the timer is awakened;
- nextWhen — When the timer is in the timerModifiedLater/timerModifiedEairlier state, it is used to set the when field;
- status — the status of the timer;
Here is just a private timer running timer representation, and the structure of the exposed timer and the structure is as follows:
type Timer struct { C <-chan Time r runtimeTimer } type Ticker struct { C <-chan Time r runtimeTimer }
The same is the time channel in the timer. Next, let’s take a look at how to use these two timers and what to pay attention to when using them.
Timer timer
The timer must be created through a function , or . When the timer fails, the invalid time will be sent to the channel held by the timer, and the goroutine that subscribes to the channel will receive the time when the timer expires.
Timer users can define their own timeout logic, especially when using select to handle timeouts of multiple channels, timeouts of single channel reading and writing, etc. Common ways to use Timer are as follows:
//use:t := (d, f) //use:select { case m := <-c: handle(m) case <-(5 * ): ("timed out") } // use:t := (5 * ) select { case m := <-c: handle(m) case <-: ("timed out") }
The Timer created in this way will execute the function f in a separate goroutine after the timeout is reached.
func AfterFunc(d Duration, f func()) *Timer { t := &Timer{ r: runtimeTimer{ when: when(d), f: goFunc, arg: f, }, } startTimer(&) return t } func goFunc(arg interface{}, seq uintptr) { go arg.(func())() }
From the source code of AfterFunc above, we can see that the f parameter passed outside is not directly assigned to the f of the runtime timer, but is passed as the parameter of the wrapper function goFunc. goFunc will start a new goroutine to execute the function f passed in externally. This is because all timer event functions are run by the unique goroutine timerproc within the Go runtime. In order not to block execution of timerproc, a new goroutine execution event function must be started.
For the two creation methods of NewTimer and After, after Timer timed out, it is that after Timer timed out, it executes a built-in function in the standard library: sendTime.
func NewTimer(d Duration) *Timer { c := make(chan Time, 1) t := &Timer{ C: c, r: runtimeTimer{ when: when(d), f: sendTime, arg: c, }, } startTimer(&) return t } func sendTime(c interface{}, seq uintptr) { select { case c.(chan Time) <- Now(): default: } }
sendTime sends the current time to the time channel of the Timer. So will this action block the execution of timerproc? The answer is no, because NewTimer creates a buffered channel, so whether the channel has a sendTime or not, it can send the current time non-blocking. In addition, double insurance is added to the sendTime: whether the buffer judged by select is full, once it is full, it will exit directly and will not block.
Timer's Stop method can prevent the timer from triggering. Calling the Stop method successfully stops the timer's triggering will return true. If the timer has expired or has been stopped by Stop, calling the Stop method again will return false.
The Go runtime maintains all timers in a minimum heap Min Heap, and Stop a timer is to delete the timer from the heap.
Ticker timer
Ticker can periodically trigger time events, each time it reaches a specified time interval.
Need to be passed or created.
// use:go func() { for t := range () { ("Tick at", t) } }() // usevar ticker * = (1 * ) go func() { for t := range { ("Tick at", t) } }() ( * 5) () ("Ticker stopped")
However, it is rarely used unless you want to use the time channel throughout the life of the program. The description in the official document is:
The underlying Ticker cannot be restored by the garbage collector;
Therefore, be careful when using it, and try to use the returned Ticker substitution to avoid accidents.
The timer created by NewTicker is the same as the time channel held by the timer created by NewTimer. The function executed after each trigger is also sendTime, which ensures that no matter the receiver Ticker triggers the time event, it will not block:
func NewTicker(d Duration) *Ticker { if d <= 0 { panic(("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(&) return t }
Issues to note when reset timer
Regarding the recommendations for using Reset, the description in the document is:
When resetting the timer, be careful not to compete with the operation where the current timer expires and sends the time to the time. If the program has received a value from receiving it, the timer is known to have expired and can be used directly. If the program has not received the value from the receiving value, the timer must be stopped first, and - if the timer has expired when used, drain its channel median value.
For example:
if !() { <- } (d)
In the following example, the producer goroutine sends a false value to the channel every second, and waits for one second after the loop to send a true value to the channel. In the consumer goroutine, try to read the value from the channel through a loop, and use the timer to set the maximum waiting time to 5 seconds. If the timer timed out, output the current time and make the next loop attempt. If the read from the channel is not the expected value (the expected value is true), try to read from the channel again and reset the timer.
func main() { c := make(chan bool) go func() { for i := 0; i < 5; i++ { ( * 1) c <- false } ( * 1) c <- true }() go func() { // try to read from channel, block at most 5s. // if timeout, print time event and go on loop. // if read a message which is not the type we want(we want true, not false), // retry to read. timer := ( * 5) for { // timer is active , not fired, stop always returns true, no problems occurs. if !() { <- } ( * 5) select { case b := <-c: if b == false { ((), ":recv false. continue") continue } //we want true, not false ((), ":recv true. return") return case <-: ((), ":timer expired") continue } } }() //to avoid that all goroutine blocks. var s string (&s) }
The output of the program is as follows:
2020-05-13 12:49:48.90292 +0800 CST m=+1.004554120 :recv false. continue
2020-05-13 12:49:49.906087 +0800 CST m=+2.007748042 :recv false. continue
2020-05-13 12:49:50.910208 +0800 CST m=+3.011892138 :recv false. continue
2020-05-13 12:49:51.914291 +0800 CST m=+4.015997373 :recv false. continue
2020-05-13 12:49:52.916762 +0800 CST m=+5.018489240 :recv false. continue
2020-05-13 12:49:53.920384 +0800 CST m=+6.022129708 :recv true. return
There is no problem at the moment, and using Reset to reset the timer also works. Next, we make some changes to the producer goroutin. We change the logic of sending values per second in the producer goroutine to send values every 6 seconds, while the timer in the consumer goroutine still expires in 5 seconds.
// producer go func() { for i := 0; i < 5; i++ { ( * 6) c <- false } ( * 6) c <- true }()
Running again will find that the program has a deadlock blocked directly after the first timer expires:
2020-05-13 13:09:11.166976 +0800 CST m=+5.005266022 :timer expired
Where did the program block? Right means blocking when draining the channel (in English, drain channel is called water in the flow channel, which means that there is no unreceived value in the pipeline in the program).
if !() { <- } ( * 5)
The sending behavior of the producer goroutine has changed. The comsumer goroutine has an event with an expiration of the timer before receiving the first data. The for loop performs a loop. At this time, the function returns no longer true, but false, because the timer has expired, and the minimum heap mentioned above that maintains all active timers is no longer included. There is no data at this time, and the next code used for drain channel will block the consumer goroutine.
In this case, we should directly reset the timer without explicit drain channel. How to combine these two situations into one? We can use a select to wrap the drain channel operation, so that the drain will not block regardless of whether there is data in the channel.
//consumer go func() { // try to read from channel, block at most 5s. // if timeout, print time event and go on loop. // if read a message which is not the type we want(we want true, not false), // retry to read. timer := ( * 5) for { // timer may be not active, and fired if !() { select { case <-: //try to drain from the channel default: } } ( * 5) select { case b := <-c: if b == false { ((), ":recv false. continue") continue } //we want true, not false ((), ":recv true. return") return case <-: ((), ":timer expired") continue } } }()
Run the modified program and find that the program will not be blocked and can be read normally through channel. After reading the true value, it will exit by itself. The output result is as follows:
2020-05-13 13:25:08.412679 +0800 CST m=+5.005475546 :timer expired
2020-05-13 13:25:09.409249 +0800 CST m=+6.002037341 :recv false. continue
2020-05-13 13:25:14.412282 +0800 CST m=+11.005029547 :timer expired
2020-05-13 13:25:15.414482 +0800 CST m=+12.007221569 :recv false. continue
2020-05-13 13:25:20.416826 +0800 CST m=+17.009524859 :timer expired
2020-05-13 13:25:21.418555 +0800 CST m=+18.011245687 :recv false. continue
2020-05-13 13:25:26.42388 +0800 CST m=+23.016530193 :timer expired
2020-05-13 13:25:27.42294 +0800 CST m=+24.015582511 :recv false. continue
2020-05-13 13:25:32.425666 +0800 CST m=+29.018267054 :timer expired
2020-05-13 13:25:33.428189 +0800 CST m=+30.020782483 :recv false. continue
2020-05-13 13:25:38.432428 +0800 CST m=+35.024980796 :timer expired
2020-05-13 13:25:39.428343 +0800 CST m=+36.020887629 :recv true. return
Summarize
The above introduces Go language timers, their usage methods and precautions in detail. To summarize, there are the following key points:
- Timer and Ticker are both implemented based on runtime timers.
- All timer event functions in the runtime are triggered by the unique goroutine timerproc in the runtime.
- The created Ticker will not be recycled by gc when running, so if you can, you won't use it.
- Timer and Ticker time channel are both with a buffered channel.
- , , when the created timer is triggered, sendTime will be executed.
- The time channel with cached sendTime and timer ensures that the timer does not block the program.
- When reset timer, be careful to see that there are race conditions for drain channel and timer expiration.
This is the end of this article about Go timers. For more information about Go timers, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!