首页 > 解决方案 > 如何停止当前正在另一个 go-routine 中收听的 time.Timer?

问题描述

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

我查看了文档,但我不确定我很清楚它说的是什么。

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

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

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

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

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

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

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

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

标签: gotimer

解决方案


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

问题

想象一下这种情况:

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

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

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

解决方案

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

如果您只是一个简单的选择:

select {
    case result <- doWork():
    case <-t.C
}

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

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

只要你对情况4没问题,你不需要在调用后交互/排空频道Stop.

如果您对案例 4 不满意。您仍然无法耗尽t.C通道,因为有另一个 goroutine 正在监听它。这可能会阻塞 if 语句。相反,您必须找到另一种布局代码的方法,或者确保您在 select 中的 goroutine 没有仍在监听通道。


推荐阅读