Principle and use of GO timer

0. Preface

When performing concurrent programming, timing functions are sometimes needed, such as monitoring whether a certain GO process will run for too long, printing logs at regular intervals, etc.

There are two main types of timers in the GO standard library, one is the Timer timer and the other is the Ticker timer. After the Timer is used once, it becomes invalid and requires Reset() to take effect again. The Ticker timer will always be in effect. Next , we will introduce the two types respectively.

1. Timer timer

First, let’s introduce the implementation principle of GO timer .
In a GO process, all timers in it are timerproc()protected by a goroutine running a function. It uses the time heap (min heap) algorithm to protect all Timers. Its underlying data structure is based on the min heap of the array. The element on the top of the heap is the Timer with the closest interval timeout. This goroutine will wake up regularly and read the top of the heap. Timer, executes the corresponding ffunction or sendtime()functions (these two functions will be introduced below), and then removes it from the top of the heap.

Next, look at the structure of Timer :

type Timer struct {
    C <-chan Time
    // contains filtered or unexported fields
}

There is only one channel exposed to the outside world in Timer, and this channel is also the core of the timer. When the timer ends, the Timer will send the value to the channel. When the external environment receives the value in this channel, it means that the timer has timed out. It can be used with select to execute some timeout logic. It can be created by time.NewTimer, time.AfterFuncor from a Timer.time.Afte

1.1 time.NewTimer() 和 time.After()

1.1.1 time.NewTimer()

Check out the simple application code below:

package main

import (
   "fmt"
   "time"
)
type H struct {
   t *time.Timer
}

func main()  {
   fmt.Println("main")
   h:=H{t: time.NewTimer(1*time.Second)}
   go h.timer()
   time.Sleep(10*time.Second)
}

func (h *H) timer()  {
   for  {
      select {
      case <-h.t.C:
         fmt.Println("timer")
      }
   }

}

We created a timer and set the time to 1S. Then use a select to accept the C of the timer. The GO process runs the timer() function and will block until the timeout occurs (the data of C is received), at which time the timer is printed.

Stop() Stop Timer

func (t *Timer) Stop() bool

Stop()It is a method of Timer. Calling Stop()the method will stop the timing of this Timer, make it invalid, and then trigger a timed event.

In fact, after calling this method, this Timer will be removed from the time heap.

Reset() resets Timer

Note that the Timer timer will not run again after it times out once, so you need to call the Reset function to reset it . Modify the code in select and add a reset code in Case:

select {
case <-h.t.C:
   fmt.Println("timer")
   h.t.Reset(1*time.Second)
}

As you can see, the timer will be printed continuously. This is because the Reset function is used to reset the timer.

Notice! The Reset method cannot be called at will . The official website document specifically emphasizes:

For a Timer created with NewTimer, Reset should be invoked only on stopped or expired timers with drained channels.

The general meaning is that unless the Timer has been stopped or timed out, do not call the Reset method, because if the Timer has not timed out yet, do not stop it first, but directly Reset, then the old Timer still exists and is still It may be triggered and something unexpected may happen. Therefore, the following code is usually used to safely reset a Timer of unknown status (in the above code, when Reset is called, it is always in a timeout state):

if !t.Stop() {
    select {
    case <-h.t.C: 
    default:
    }
}
h.t.Reset(1*time.Second)

1.1.2 time.After()

This method is like a minimalist version of Timer. When called time.After(), it will directly return a channel. When it times out, this channel will receive a value. Simply use it as follows:

package main

import (
	"fmt"
	"time"
)

func main() {
	fmt.Println("main")
	go ticker()
	time.Sleep(100 * time.Second)
}

func ticker() {
	for {
		select {
		case <-time.After(1 * time.Second):
			fmt.Println("timer")
		}
	}
}

Note that although this method is simple, there is no Reset method to reset the timer, but it can be combined with repeated calls of for and select to simulate a reset.

1.1.3 sendtime function

NewTimerThese Aftertwo creation methods will Timerexecute a built-in function in the standard library after timeout: sendTimeto send the current time to the channel.

1.2 time.AfterFunc

This method can accept a func type parameter. After the timer expires, this function will be run. Check the following code. Guess what the result will be?

package main

import (
   "fmt"
   "time"
)

func main() {
   fmt.Println("main")
   t := time.AfterFunc(1*time.Second, func() {
      fmt.Println("timer")
   })
   go timer(t)
   time.Sleep(10 * time.Second)
}
func timer(t *time.Timer) {
   select {
   case <-t.C:
      fmt.Println("123")
   }
}

As a result, only main and timer are printed. This is because this method does not call the sendtime() function mentioned above, that is, it does not send the value to the Timer's Channel, so the select will always block.

f function

AfterFuncThe sum above is deliberately NewTimeradded Afterbecause of the existence of the f function. Created in this way Timer, after the timeout period is reached , goroutinethe function fwill be executed in a separate file instead of the sendtime function.

Note that the parameters passed in from the outside fare not directly run in the timerprocmethod, but a new one is started goroutineto execute this method.

2. Ticker timer

TickerThe timer can continuously trigger time events periodically without requiring additional Reset operations.

Its usage is similar to that of Timer. By time.NewTickercreating a Ticker, simply use it as follows:

package main

import (
   "fmt"
   "time"
)

func main() {
   fmt.Println("main")
   t:=time.NewTicker(1*time.Second)
   go timer(t)
   time.Sleep(10 * time.Second)
}
func timer(t *time.Ticker) {
   for{
      select {
      case <-t.C:
         fmt.Println("timer")
      }
   }
}

Guess you like

Origin blog.csdn.net/doreen211/article/details/125379493