zoukankan      html  css  js  c++  java
  • 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)
    }
    

        

  • 相关阅读:
    Spring Boot属性配置文件详解
    Spring Boot中使用@Scheduled创建定时任务
    Spring Boot中使用@Async实现异步调用
    Spring boot中使用log4j记录日志
    Spring Boot中对log4j进行多环境不同日志级别的控制
    Spring Boot中使用AOP统一处理Web请求日志
    在Windows下安装MongoDB
    MongoDB中的基础概念:Databases、Collections、Documents
    Spring Boot中使用log4j实现http请求日志入mongodb
    Spring Boot中的事务管理
  • 原文地址:https://www.cnblogs.com/jiangz222/p/11622495.html
Copyright © 2011-2022 走看看