停止时进入计时器死锁

时间:2019-03-28 14:54:14

标签: go

我试图通过停止并重置计时器来重用计时器。我正在遵循文档提供的模式。这是一个可以在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")
}

2 个答案:

答案 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()

这将正确执行此操作,并且永远不会挂起。但是,可悲的是,它不存在。

这是命中注定的解决方法,它是第二条评论内容,它使这项工作得以解决。取消注释,这将工作,并按照您想要的方式进行-确保计时器已停止,通道已排空并且整个东西都可以重新使用。