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

    

猜你喜欢

转载自www.cnblogs.com/jiangz222/p/11622495.html