dtmsaqtly798322992 2018-06-28 09:55
浏览 64
已采纳

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

I have an idle timeout timer being selected on in a goroutine, if I see activity I want to cancel the timer.

I had a look at the documentation and I'm not positive I'm clear on what it says.

func (t *Timer) Stop() bool
Stop prevents the Timer from firing. It returns true if the call stops the timer, false if the timer has already expired or been stopped. Stop does not close the channel, to prevent a read from the channel succeeding incorrectly.

To prevent a timer created with NewTimer from firing after a call to Stop, check the return value and drain the channel. For example, assuming the program has not received from t.C already:

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

This cannot be done concurrent to other receives from the Timer's channel.

I'm trying to understand when I have to drain the channel manually.

I'll list my understanding and if I'm wrong please correct me.

If Stop returned false this means either:

  • The timer was already stopped
    • In this case, reading from the channel will block so I shouldn't do it
  • The timer was already expired
    • Since I have another goroutine listening on the channel, can I know for certain if it has received the event?
    • From the "this cannot be done concurrent to other receives" part of the documentation it appears that this isn't an option, so what should I do?

In my case getting a superfluous event from the timer is no big deal, does that inform what I should do here?

  • 写回答

1条回答 默认 最新

  • douhuan1257 2018-06-28 10:57
    关注

    The reason that you might need to drain the channel is because of how goroutines are scheduled.

    Problem

    Imagine this case:

    1. Created a timer
    2. Timer fires, sending a value to t.C
    3. Nothing has received / read the value yet, but t.Stop() is called.

    In this case there is a value on the channel t.C, and t.Stop() returns false because "the timer already expired" (i.e. when it sent the value on t.C).

    The reason that the docs say "this cannot be done concurrent to other receives" is because there's not guarantee of ordering between the if !t.Stop { and the <-t.C. The stop command could return false, entering the if body. And then another goroutine could be scheduled and read the value from t.C that the body of the if statement was trying to drain. This would cause a datarace and result in blocking inside the if statement. (as you pointed out in your question!)

    Solution

    It depends on what the behaviour of the thing listening to the timer is.

    If you are just in a simple select:

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

    Something like above. One of a few things could happen:

    1. The work could be done from doWork, sending the result, all good.
    2. The timer could fire, causing the timeout, breaking the select.
    3. Call to stop, stop the timer, only way out of the select is doWork() completing.
    4. The timer could fire, another routine calls t.Stop(), but it's too late because the value has been sent, causing the timeout, breaking the select.

    As long as you are OK with case 4, you do not need to interact / drain the channel after calling Stop.

    If you are not OK with case 4. You still cannot drain the t.C channel because there's another goroutine listening to it. This could block in the if statement. Instead you must find another way of laying out the code, or ensuring that your goroutine in the select is not still listening on the channel.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 删除虚拟显示器驱动 删除所有 Xorg 配置文件 删除显示器缓存文件 重启系统 可是依旧无法退出虚拟显示器
  • ¥15 vscode程序一直报同样的错,如何解决?
  • ¥15 关于使用unity中遇到的问题
  • ¥15 开放世界如何写线性关卡的用例(类似原神)
  • ¥15 关于并联谐振电磁感应加热
  • ¥60 请查询全国几个煤炭大省近十年的煤炭铁路及公路的货物周转量
  • ¥15 请帮我看看我这道c语言题到底漏了哪种情况吧!
  • ¥66 如何制作支付宝扫码跳转到发红包界面
  • ¥15 pnpm 下载element-plus
  • ¥15 解决编写PyDracula时遇到的问题