如何停止当前正在另一个 go-routine 中监听的 time.Timer?

Mot*_*tti 4 timer go

我在 goroutine 中有一个空闲超时计时器select,如果我看到活动,我想取消计时器。

我查看了文档,但我不确定我是否清楚它所说的内容。

func (t *Timer) Stop() bool
停止可防止定时器触发。如果调用停止计时器,则返回 true,如果计时器已过期或已停止,则返回 false。停止不会关闭通道,以防止从通道读取不正确地成功。

要防止使用 NewTimer 创建的计时器在调用 Stop 后触发,请检查返回值并排空通道。例如,假设程序尚未从 tC 收到:

if !t.Stop() { <-t.C }

这不能与来自定时器通道的其他接收同时进行。

我试图了解何时必须手动排空通道。

我会列出我的理解,如果我错了,请纠正我。

如果Stop返回,false这意味着:

  • 计时器已经停止
    • 在这种情况下,从频道读取会阻塞,所以我不应该这样做
  • 计时器已经过期
    • 由于我有另一个 goroutine 在监听通道,我能确定它是否收到了事件吗?
    • 从文档的“这不能与其他接收同时完成”部分看来,这不是一个选项,那么我该怎么办?

在我的情况下,从计时器获得多余的事件没什么大不了的,这是否告诉我应该在这里做什么?

Zak*_*Zak 7

您可能需要排空通道的原因是 goroutine 的调度方式。

问题

想象一下这个案例:

  1. 创建了一个计时器
  2. 定时器触发,发送一个值到 t.C
  3. 尚未接收/读取该值,但t.Stop()已被调用。

在这种情况下, channel 上有一个值t.C,并t.Stop()返回 false 因为“计时器已经过期”(即当它在 上发送值时t.C)。

文档说“这不能与其他接收同时进行”的原因是因为不能保证if !t.Stop {<-t.C. stop 命令可能返回 false,进入 if 主体。然后可以调度另一个 goroutine 并从t.Cif 语句的主体试图排出的值中读取值。这将导致数据争用并导致 if 语句内的阻塞。(正如您在问题中指出的那样!)

解决方案

这取决于监听计时器的事物的行为是什么。

如果你只是在一个简单的选择中:

select {
    case result <- doWork():
    case <-t.C
}
Run Code Online (Sandbox Code Playgroud)

类似上面的东西。可能会发生以下几种情况之一:

  1. 工作可以从doWork,发送结果,一切都很好。
  2. 计时器可能会触发,导致超时,破坏选择。
  3. 呼叫停止,停止计时器,唯一的出路是选择doWork()完成。
  4. 计时器可能会触发,另一个例程调用t.Stop(),但为时已晚,因为值已发送,导致超时,中断选择。

只要你对case 4没问题,调用后不需要交互/drain channel Stop.

如果您对案例 4t.C不满意。您仍然无法排空通道,因为还有另一个 goroutine 在监听它。这可能会阻塞 if 语句。相反,您必须找到另一种方式来布置代码,或者确保选择中的 goroutine 不会仍在侦听通道。