我试图通过停止并重置计时器来重用计时器。我正在遵循文档提供的模式。这是一个可以在go Playground中运行的简单示例,演示了我遇到的问题。
是否有正确的方法来停止和重置不涉及死锁或竞争条件的计时器?我知道,将select与默认值配合使用会在频道消息传递时间上产生竞争条件,因此不能依赖。
package main
import (
"fmt"
"time"
"sync"
)
func main() {
fmt.Println("Hello, playground")
timer := time.NewTimer(1 * time.Second)
wg := &sync.WaitGroup{}
wg.Add(1)
go func(_wg *sync.WaitGroup) {
<- timer.C
fmt.Println("Timer done")
_wg.Done()
}(wg)
wg.Wait()
fmt.Println("Checking timer")
if !timer.Stop() {
<- timer.C
}
fmt.Println("Done")
}
答案 0 :(得分:1)
根据timer.Stop文档,有一个关于耗尽渠道的警告:
假设尚未从t.C收到该程序的未 ...
这不能与定时器的其他接收同时进行 频道。
由于该频道已被耗尽-不再触发,因此第二个<-timer.C
将永远阻塞。
答案 1 :(得分:0)
该问题首先问计时器为什么挂起。这是一个很好的问题,因为即使用户程序中没有错误,在名为Go.Timer的东西中,至少还是有一些奇怪的含糊之处。规范中明确指出:
停止可阻止计时器触发。如果通话停止,则返回true 计时器,如果计时器已过期或已停止,则为false。 停止不会关闭通道,以防止从通道读取 成功。
要确保在调用Stop之后通道是空的,请检查返回 值并耗尽通道。例如,假设程序没有 已从t.C收到
if !t.Stop() { <-t.C }
这不能与定时器的其他接收同时进行 渠道或对Timer的Stop方法的其他调用。
有简短而精确的单词,但要理解它们可能并不那么容易(至少对我而言)。我试图在一段代码中重复使用Timer,并在每次下一次使用之前将其重置。每次这样做,我可能都想Stop()-可以肯定。上面的规范暗示了您应该如何做,并提供了一个示例-可能不起作用!这取决于您尝试应用Stop惯用语的位置。如果您已经在此计时器的选择情况下执行此操作,那么它将挂起程序。
具体地说,我没有任何并发接收器,只有一个goroutine。因此,让我们制作一个简单的测试程序,并尝试对其进行试验(https://play.golang.org/p/d7BlNReE9Jz):
package main
import (
"fmt"
"time"
)
func main() {
i := 0
d2s := time.Second * 1
i++; fmt.Println(i)
t := time.NewTimer(d2s)
<-t.C
i++; fmt.Println(i)
t.Reset(d2s)
<-t.C
i++; fmt.Println(i)
// if !t.Stop() { <-t.C }
// if !t.Stop() { select { case <-t.C: default: } }
t.Reset(d2s)
<-t.C
i++; fmt.Println(i)
}
此代码有效。它会打印1,2,3,4,延迟1秒,这就是预期的打印速度。到目前为止一切顺利。
现在,尝试取消注释第一条注释行。现在的事情是:根据规范,它是100%正确(是吗?),并且必须可以工作,但事实并非如此,然后挂起。为什么?因为,根据规范,它必须挂起!我已经阅读了该频道,并且计时器已停止,因此if
会触发,并且频道消耗操作会挂起。
这是一个错误吗?否。规格不对吗?不,是正确的。但是,这与典型的计时器用户想要的相反。 (也许是提议去的主题?)。我们需要的是这样的东西:
t.SafeStopDrain()
这将正确执行此操作,并且永远不会挂起。但是,可悲的是,它不存在。
这是命中注定的解决方法,它是第二条评论内容,它使这项工作得以解决。取消注释,这将工作,并按照您想要的方式进行-确保计时器已停止,通道已排空并且整个东西都可以重新使用。