【问题标题】:Ticker Stop behaviour in GolangGolang 中的 Ticker Stop 行为
【发布时间】:2013-07-22 21:28:53
【问题描述】:

如果我在一个股票频道上进行测距并调用 stop() 频道将停止但未关闭。

在这个例子中:

package main

import (
    "time"
    "log"
)

func main() {
    ticker := time.NewTicker(1 * time.Second)
    go func(){
        for _ = range ticker.C {
            log.Println("tick")
        }
        log.Println("stopped")
    }()
    time.Sleep(3 * time.Second)
    log.Println("stopping ticker")
    ticker.Stop()
    time.Sleep(3 * time.Second)
}

运行产生:

2013/07/22 14:26:53 tick
2013/07/22 14:26:54 tick
2013/07/22 14:26:55 tick
2013/07/22 14:26:55 stopping ticker

所以 goroutine 永远不会退出。有没有更好的方法来处理这种情况?我应该关心 goroutine 从未退出吗?

【问题讨论】:

  • 如果 go 例程没有退出,你会得到内存泄漏。调用 close(ticker.C) 以释放 Go 例程。
  • 无法关闭:“无法关闭仅接收通道”
  • golang 文档应该对此进行真正的扩展,但他们的 Ticker 示例确实展示了如何使用 done 通道来确保没有 go-routine 泄漏:golang.org/pkg/time/#example_NewTicker

标签: go ticker


【解决方案1】:

使用了第二个频道as Volker suggested。这就是我最终运行的结果:

package main

import (
    "log"
    "time"
)

// Run the function every tick
// Return false from the func to stop the ticker
func Every(duration time.Duration, work func(time.Time) bool) chan bool {
    ticker := time.NewTicker(duration)
    stop := make(chan bool, 1)

    go func() {
        defer log.Println("ticker stopped")
        for {
            select {
            case time := <-ticker.C:
                if !work(time) {
                    stop <- true
                }
            case <-stop:
                return
            }
        }
    }()

    return stop
}

func main() {
    stop := Every(1*time.Second, func(time.Time) bool {
        log.Println("tick")
        return true
    })

    time.Sleep(3 * time.Second)
    log.Println("stopping ticker")
    stop <- true
    time.Sleep(3 * time.Second)
}

【讨论】:

  • 如果你的工作需要 4 秒,你会死锁你的 goroutine,它会被卡住试图写入它是唯一读者的频道。你真的只需要for{} 上的状态变量——不要在停止通道上发送,只需关闭它。
  • 还有nil 通道是向任何其他通道发出退出操作(close(quit))信号之后 到任何其他通道读取的好方法:参见nil channels 与@987654323 @.
【解决方案2】:

在第二个通道上发出“done”信号,然后在你的 goroutine 中选择 ticker 和 done 通道。

根据您真正想做的事情,可能存在更好的解决方案,但这很难从简化的演示代码中看出。

【讨论】:

    【解决方案3】:

    你可以这样做。

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func startTicker(f func()) chan bool {
        done := make(chan bool, 1)
        go func() {
            ticker := time.NewTicker(time.Second * 1)
            defer ticker.Stop()
            for {
                select {
                case <-ticker.C:
                    f()
                case <-done:
                    fmt.Println("done")
                    return
                }
            }
        }()
        return done
    }
    
    func main() {
        done := startTicker(func() {
            fmt.Println("tick...")
        })
        time.Sleep(5 * time.Second)
        close(done)
        time.Sleep(5 * time.Second)
    }
    

    【讨论】:

      【解决方案4】:

      如果您需要节省更多空间,请使用不占用内存的空结构 (struct{}) 的通道。如上所述,不要在其中发送任何东西 - 只需关闭,它实际上发送零值。

      【讨论】:

      • 不知道为什么这被否决了:quit := make(chan struct{}) 是一项很棒的技术,并且清楚地表明了通道的用途,即通过 close(quit) 发出关闭信号
      猜你喜欢
      • 2022-01-18
      • 2014-09-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-11
      相关资源
      最近更新 更多