go time.Ticker与time.Timer使用

一篇速记

1. time.Ticker的正确使用方法

copy from go example

func main() {
  
    // Calling NewTicker method
    Ticker := time.NewTicker(2 * time.Second)
  
    // Creating channel using make
    // keyword
    mychannel := make(chan bool)
  
    // Go function
    go func() {
  
        // Using for loop
        for {
  
            // Select statement
            select {
  
            // Case statement
            case <-mychannel:
                return
  
            // Case to print current time
            case tm := <-Ticker.C:
                fmt.Println("The Current time is: ", tm)
            }
        }
    }()
  
    // Calling Sleep() method
    time.Sleep(7 * time.Second)
  
    // Calling Stop() method
    Ticker.Stop()
  
    // Setting the value of channel
    mychannel <- true
  
    // Printed when the ticker is turned off
    fmt.Println("Ticker is turned off!")
}

需要注意的一个事情是,ticker.stop不会关闭channel,只是保证不会向channel中发送新的数据而已,因此需要额外使用一个channel来显式地通知routine退出。

2. time.Timer的正确使用方法

和ticker类似,timer也有stop, reset两种方法,但timer的reset的方法使用频率会更高一些(重置时钟)。
time的文档中推荐采用如下的方式使用stop这种情况下不能Stop两次,或者说不能保证并发安全,因为如果Stop返回false的原因是因为之前已经被stop一次了,而且timer.C中没有元素,会导致该协程死锁,因为stop函数不会关闭channel)。

// stop() returns true if the call stops the timer, 
// false if the timer has already expired or been stopped.
if !timer.Stop() {
	<-timer.C  
}

使用reset函数也需要十分小心,文档中推荐采用如下方式使用reset。同样的,这样的使用方法不是并发安全的。文档中强调了不能使用Reset的返回值(如果是一个active的时钟返回true,如果该时钟已经被stop或者expired返回false)来判断是否需要读取掉channel中的一个元素,因为可能因为协程的调度或者GC的运行导致在Reset返回false,且返回前新时钟就过期了,这时如果原来channel中没有元素就会出问题

if !timer.Stop() {
	<-timer.C
}
timer.Reset(d)
//---------------------------错误的使用方法
if !timer.Reset(d){
	<- timer.C
}

3. 为什么要如此设计

主要原因来自go中读取channel的语义,向一个已经关闭的channel写会导致panic,但是向一个已经关闭且没有值的channel会返回零值。因此如果stop函数语义中添加关闭channel的语义有可能给已有的应用造成问题(应用需要增加显式地判断channel返回值是否为零值的逻辑)。
引自github上的一个issue:

This came up once before and we decided not to do this. The problem is that closing the channel makes any pending receive unblock and deliver a zero value, which could cause significant confusion in existing code. In general channels do not have to be closed (they are not like file descriptors), and there is no real reason to close this one. In particular there are no range loops involved.

你可能感兴趣的:(6.824,与,go,golang)