【问题标题】:How to stop a timer correctly?如何正确停止计时器?
【发布时间】:2018-10-17 19:47:20
【问题描述】:
var timer *time.Timer

func A() {
    timer.Stop() // cancel old timer
    go B() // new timer
}

func B() {
    timer = time.NewTimer(100 * time.Millisecond)
    select {
    case <- timer.C:
    // do something for timeout, like change state
    }
}

函数 A 和 B 都在不同的 goroutine 中。

假设 A 在 RPC goroutine 中。当应用程序收到 RPC 请求时,它会取消 B 中的旧定时器,并在另一个 goroutine 中启动一个新的定时器。

医生说:

Stop 不会关闭通道,以防止从通道读取 错误地成功了。

那么如何打破B中的select以避免goroutine泄漏?

【问题讨论】:

  • 请描述您要解决的高级问题。
  • 你能详细说明你的问题吗?
  • @aerokite 超时时,切换到下一个状态。当收到请求时,保持状态并启动一个新的计时器。
  • @lxyscls 你对这段代码有什么真正的问题? 100ms 定时器不会停止。那又怎样?
  • 只需使用取消渠道并同时选择

标签: go goroutine


【解决方案1】:

除了上面的答案,如果你想一次取消所有服务员,你可以使用你自己的可以取消的计时器机制来封装行为,在After 频道中发送 true 或 false 来告诉你是否对于所有服务员,他们正在从取消或超时中醒来。

package main

import (
    "fmt"
    "time"
)

type CancellableTimer struct {
    cancel chan bool
}

func NewCancellableTimer() *CancellableTimer {
    return &CancellableTimer{
        cancel: make(chan bool),
    }
}

// internal wait goroutine wrapping time.After
func (c *CancellableTimer) wait(d time.Duration, ch chan bool) {
    select {
    case <-time.After(d):
        ch <- true
    case <-c.cancel:
        ch <- false
    }
}

// After mimics time.After but returns bool to signify whether we timed out or cancelled
func (c *CancellableTimer) After(d time.Duration) chan bool {
    ch := make(chan bool)
    go c.wait(d, ch)
    return ch
}

// Cancel makes all the waiters receive false
func (c *CancellableTimer) Cancel() {
    close(c.cancel)

}

// a goroutine waiting for cancellation
func B(t *CancellableTimer) {
    select {
    // timedOut will signify a timeout or cancellation
    case timedOut := <-t.After(time.Second):
        if timedOut {
            fmt.Println("Time out!")
        } else {
            fmt.Println("Cancelled!")
        }
    }
}

func main() {
    t := NewCancellableTimer()
    // Start 3 goroutines that wait for different timeouts on the same timer
    go B(t)
    go B(t)
    go B(t)

    // sleep a bit before cancelling
    time.Sleep(100 * time.Millisecond)

    // cancel the timer and all its waiters
    t.Cancel()

    // this is just to collect the output
    time.Sleep(time.Second)

}

输出:

Cancelled!
Cancelled!
Cancelled!

游乐场链接:

https://play.golang.org/p/z8OscJCXTvD

【讨论】:

    【解决方案2】:

    使用额外的、独立的取消信号。既然您已经有了一个 select 语句,那么另一个通道是一个明显的选择:

    import "time"
    
    var timer *time.Timer
    var canceled = make(chan struct{})
    
    func A() {
        // cancel all current Bs
        select {
        case canceled <- struct{}{}:
        default:
        }   
    
        timer.Stop()
    
        go B()       // new timer
    }
    
    func B() {
        timer = time.NewTimer(100 * time.Millisecond)
        select {
        case <-timer.C:
            // do something for timeout, like change state
        case <-canceled:
            // timer aborted
        }
    }
    

    请注意,所有的 As 和 B 都会相互竞争计时器值。使用上面的代码,不需要让 A 停止计时器,因此您不需要全局计时器,从而消除了比赛:

    import "time"
    
    var canceled = make(chan struct{})
    
    func A() {
        // cancel all current Bs
        select {
        case canceled <- struct{}{}:
        default:
        }
    
        go B()
    }
    
    func B() {
        select {
        case <-time.After(100 * time.Millisecond):
            // do something for timeout, like change state
        case <-canceled:
            // aborted
        }
    }
    

    【讨论】:

    • 我怀疑“已取消
    • @lxyscls 如果你关闭取消通道而不是推送到它,每个 goroutine 都会退出。
    • @lxyscls,你是对的。不知道我在想什么。但是关闭频道也会中止所有未来的 B。
    猜你喜欢
    • 2015-07-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-15
    • 2012-05-05
    • 2014-08-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多