当前位置:网站首页>Important knowledge of golang: timer timer

Important knowledge of golang: timer timer

2022-06-23 15:24:00 yue_ xin_ tech

Abstract

stay Go There are many ways to use timers in , Like regular Timer、Ticker object , And what you often see time.After(d Duration) and time.Sleep(d Duration) Method , Today we will introduce their usage and analyze their underlying source code , In order to use the timer in better scenarios .

Go The timer in the room

Let's take a look first Timer object as well as time.After Method , They all have the feature of one-time use . about Timer Come on , After using it, you can enable it again , Just call its Reset Method .

// Timer  Example 
func main() {
    
	myTimer := time.NewTimer(time.Second * 5) //  Start timer 

	for {
    
		select {
    
		case <-myTimer.C:
			dosomething()
			myTimer.Reset(time.Second * 5) //  It needs to be reset manually after each use 
		}
	}

	//  No longer use , End it 
	myTimer.Stop()
}
// time.After  Example 
func main() {
    
  timeChannel := time.After(10 *  time.Second)
  select {
    
  	case <-timeChannel:
	   doSomething()
  }
}

You can see that from the top Timer Allow to be enabled again , and time.After Back to a channel, Will not be reusable .

And it's important to note that time.After Essentially, it creates a new Timer Structure , It's just that what is exposed is in the structure channel It's just a field .

So if the for{...} It is recycled in time.After, Will continue to create Timer. The following usage methods will cause performance problems :

//  Wrong case  !!!
	func main() {
    
		for {
     // for  Inside  time.After  Will continue to create  Timer  object 
			select {
    
				case <-time.After(10 * time.Second):
				doSomething()
			}
		}
	}

After reading it, I have “ Primary characteristics ” Timer for , Next, let's take a look at the timer that repeats the task at certain intervals :

	func main() {
    
		ticker := time.NewTicker(3 * time.Second)
		for {
    
			<-ticker.C
			doSomething()
		}
		ticker.Stop()
	}

there Ticker Follow Timer The difference , It's about Ticker There is no need to call after the time Reset Method , Will automatically renew .

In addition to the timer above ,Go Inside time.Sleep It also has a timing function similar to one-time use . It's just time.Sleep System call is used . Timers like the one above rely more on Go The scheduling behavior of .

Realization principle

When we go through NewTimer、NewTicker And so on , Back to a Timer object . There is one in this object runtimeTimer The structure of the field , It will eventually be compiled into src/runtime/time.go Inside timer Structure .

And this timer A structure is a structure with real timing processing logic .

In limine ,timer Will be assigned to a global timersBucket Time bucket . Whenever there is timer When created , Will be allocated to the corresponding time bucket .

In order not to let all timer All in one time bucket ,Go Will create 64 Such a time bucket , And then according to At present timer Where Goroutine Of P Of id Go hash to a bucket :

// assignBucket  Will create the  timer  Linked to a bucket 
func (t *timer) assignBucket() *timersBucket {
    
	id := uint8(getg().m.p.ptr().id) % timersLen
	t.tb = &timers[id].timersBucket
	return t.tb
}

next timersBucket The time bucket will be for these timer Maintain a minimum heap , Each time, I will choose the one that the time is closest to reaching timer.

If the selected timer It's not time yet , Then it will go on sleep Sleep .

If timer It's time to , execute timer The function on , And go to timer Of channel Field send data , In order to inform timer Where goroutine.

Source code analysis

The principle of the lower timer is mentioned above , Now let's take a good look at the timer timer Source code .

First , When timer is created , Would call startTimer Method :

func startTimer(t *timer) {
    
	if raceenabled {
    
		racerelease(unsafe.Pointer(t))
	}
	// 1. Start putting the current  timer  Add to   In the time bucket 
	addtimer(t)
}

and addtimer That is what we just said about the action of allocating to a bucket :

func addtimer(t *timer) {
    
	tb := t.assignBucket() //  Allocate to a time bucket 
	lock(&tb.lock)
	ok := tb.addtimerLocked(t) // 2. After adding , The time bucket performs a heap sort , Pick the nearest  timer  To carry out 
	unlock(&tb.lock)
	if !ok {
    
		badTimer()
	}
}

addtimerLocked Contains the final time processing function : timerproc, Focus on the analysis of :

//  When there is a new  timer  Triggered once when added 
//  When the last time to sleep comes , It will also trigger once 
func timerproc(tb *timersBucket) {
    
	tb.gp = getg()
	for {
    
		lock(&tb.lock)
		tb.sleeping = false
		now := nanotime()
		delta := int64(-1)
		for {
    
			if len(tb.t) == 0 {
    
				delta = -1
				break
			}
			t := tb.t[0]
			delta = t.when - now
			if delta > 0 {
     //  The timer is not up yet 
				break
			}
			ok := true
			if t.period > 0 {
     //  here  period > 0, Said is  ticker  Type of timer ,
				//  Reset the time of the next call , help  ticker  Automatic renewal 
				t.when += t.period * (1 + -delta/t.period)
				if !siftdownTimer(tb.t, 0) {
    
					ok = false
				}
			} else {
    
				// “ Disposable ”  Timer , And it's time , It needs to be removed first , Then do the following actions 
				last := len(tb.t) - 1
				if last > 0 {
    
					tb.t[0] = tb.t[last]
					tb.t[0].i = 0
				}
				tb.t[last] = nil
				tb.t = tb.t[:last]
				if last > 0 {
    
					if !siftdownTimer(tb.t, 0) {
    
						ok = false
					}
				}
				t.i = -1 //  Mark cleared 
			}

			//  This indicates that the timer time has expired , You need to execute the corresponding function .
			//  This function is just  sendTime, It will go there  timer  Of  channel  send data ,
			//  To notify the corresponding  goroutine
			f := t.f
			arg := t.arg
			seq := t.seq
			unlock(&tb.lock)
			if !ok {
    
				badTimer()
			}
			if raceenabled {
    
				raceacquire(unsafe.Pointer(t))
			}
			f(arg, seq)
			lock(&tb.lock)
		}
		if delta < 0 || faketime > 0 {
     //  There is no timer to perform tasks , use  gopark  Sleep 
			// No timers left - put goroutine to sleep.
			tb.rescheduling = true
			goparkunlock(&tb.lock, waitReasonTimerGoroutineIdle, traceEvGoBlock, 1)
			continue
		}
		//  Yes  timer  But it's not time yet , So we use  notetsleepg  Sleep 
		tb.sleeping = true
		tb.sleepUntil = now + delta
		noteclear(&tb.waitnote)
		unlock(&tb.lock)
		notetsleepg(&tb.waitnote, delta)
	}
}

In the code above , I found that there was no timer in the time bucket ,goroutine Would call gopark De dormancy , Until there is a new one timer Add to time bucket , To recall the loop code that executes the timer .

in addition , When the timer selected by heap sorting has not expired , It will call notetsleepg To sleep , Wait until the hibernation time is up and be aroused again .

summary

Go The timer uses heap sorting to pick the most recent timer, And will go to timer Of channel Field send data , To notify the corresponding goroutine So let's keep going .

This is the basic principle of timer , Other processes are just the execution of sleep arousal , I hope this article can help you to Go The understanding of timers !!!


Interested friends can search the official account 「 Read new technology 」, Pay attention to more push articles .
If you can , Just like it by the way 、 Leave a message 、 Under the share , Thank you for your support !
Read new technology , Read more new knowledge .
 Read new technology

原网站

版权声明
本文为[yue_ xin_ tech]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/174/202206231438381275.html