golang的timer一些坑


本文代碼部分基於dive-to-gosync-workshop的代碼

Golang 的NewTimer方法調用后,生成的timer會放入最小堆,一個后台goroutine會掃描這個堆,將到時的timer進行回調和channel(下面代碼的 c := make(chan Time,1) )寫入

// 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
}

  而golang的timer的Stop方法, 是只負責把timer從堆里移除,不負責close 上面的channel(為什么不close channel?目前看只是為了超時時, 底層代碼處理簡單不crash。其實golang官方是可以做到的超時時正確處理channel的),這樣就買下了一些坑。

    下面的代碼示范了這些坑和處理方法,其中 wrongResetAfterFired(..) 說明了超時后的channel被寫入,如果沒有被主動的正確接收,會導致的reset后的timer依然從channel拿到上一次的通道數據。

   而wrongStopMore(...) 說明,如果channel沒有被寫入,也不要直接去等待,會導致deadlock

 

 

 

package main

import (
	"fmt"
	"log"
	"time"
)

// [jz] 關於timer一個比較重要的點是,newtimer后,timer會放入最小堆,然后有一個goroutine來掃描,到期的進行回調和channel寫入
// 	stop只負責將timer從堆刪除,不負責close channel
func main() {
	log.Println("✔︎ resetBeforeFired")
	resetBeforeFired()
	fmt.Println()

	log.Println("✘ wrongResetAfterFired")
	wrongResetAfterFired()
	fmt.Println()

	log.Println("✔︎ correctResetAfterFired")
	correctResetAfterFired()
	fmt.Println()

	log.Println("✔︎ stop n times")
	stopMore()
	fmt.Println()

	log.Println("✘ stop n times but with drain")
	wrongStopMore()
	fmt.Println()

	log.Println("✘ too many receiving")
	wrongReceiveMore()
}

func resetBeforeFired() {
	timer := time.NewTimer(5 * time.Second)
	b := timer.Stop()
	log.Printf("stop: %t", b)
	timer.Reset(1 * time.Second)
	t := <-timer.C
	log.Printf("fired at %s", t.String())
}

func wrongResetAfterFired() {
	timer := time.NewTimer(5 * time.Millisecond)
	time.Sleep(time.Second) // sleep 1s能保證上面的timer 超時,channel被寫入

	b := timer.Stop()
	log.Printf("stop: %t", b)
	tt := timer.Reset(10 * time.Second)
	fmt.Println(tt)
	// 此時拿到的是第一個timer(5毫秒那個)的timeout的channel值
	t := <-timer.C
	log.Printf("fired at %s", t.String())
}

func correctResetAfterFired() {
	timer := time.NewTimer(5 * time.Millisecond)
	time.Sleep(time.Second)

	b := timer.Stop()
	log.Printf("stop: %t", b)
	// 如果stop的時候發現已經超時,此時要把channel里的寫入讀出,免得后面reset時讀出之前的channel里的值
	if !b {
		t := <-timer.C
		fmt.Println(t.String())
	}
	log.Printf("reset")
	timer.Reset(10 * time.Second)
	t := <-timer.C
	log.Printf("fired at %s", t.String())
}

func wrongReceiveMore() {
	timer := time.NewTimer(5 * time.Millisecond)
	t := <-timer.C
	log.Printf("fired at %s", t.String())

	t = <-timer.C
	log.Printf("receive again at %s", t.String())
}

func stopMore() {
	timer := time.NewTimer(5 * time.Millisecond)
	b := timer.Stop()
	log.Printf("stop: %t", b)
	time.Sleep(time.Second)
	b = timer.Stop()
	log.Printf("stop more: %t", b)
}

/*
	newtimer后,timer會放入最小堆,然后有一個goroutine來掃描,到期的進行回調和channel寫入
 	stop只負責將timer從堆刪除,不負責close channel
*/
func wrongStopMore() {
	timer := time.NewTimer(5 * time.Millisecond)
	b := timer.Stop()
	log.Printf("stop: %t", b)
	time.Sleep(time.Second)
	b = timer.Stop()
	if !b { // 可以考慮這樣解決:if !b && len(timer.C) > 0
		// 之所以出問題,是因為,第一次Stop調用,發生在timer超時前,此時timer已經從堆刪除,而timer本身沒有超時,所以不需要發送channel
		// 此時你去等待timer.C是不會有結果的
		// 比如你在第一個timer.Stop前sleep 1s,讓timer超時,channel會被寫入,此時等待timer	.C就不會有問題
		<-timer.C
	}
	time.Sleep(1 * time.Second)
	log.Printf("stop more: %t", b)
}

 

    


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM