【问题标题】:dynamically change ticker interval动态更改代码间隔
【发布时间】:2016-08-09 22:37:35
【问题描述】:

我想动态更改我的代码间隔。

我写了一个例子来告诉你我是怎么做的。我的用例不是“加速度计”,但我希望它能给你一个想法。

http://play.golang.org/p/6ANFnoE6pA

package main

import (
    "time"
    "log"
    "fmt"
)

func main() {
    interval := float64(1000)

    ticker := time.NewTicker(time.Duration(interval) * time.Millisecond)
    go func(){
        counter := 1.0
        for range ticker.C {
            log.Println("ticker accelerating to " + fmt.Sprint(interval/counter) + " ms")
            ticker = time.NewTicker(time.Duration(interval/counter) * time.Millisecond)
            counter++
        }
        log.Println("stopped")
    }()
    time.Sleep(5 * time.Second)
    log.Println("stopping ticker")
    ticker.Stop()
}

问题在于,自动收报机总是每秒钟“滴答”一次,并且不会加速……知道吗?

【问题讨论】:

  • 代码存在数据竞争
  • 因为 for 循环仍在使用旧股票代码对象的通道,而不是新股票对象的通道。
  • 感谢您的反馈。 @BleedingFingers 我看到了数据竞争(在股票代码上),但在这种情况下不应该恐慌吗?否则指针应该用新的替换。 @NipunTalukdar 我也有同样的想法,如果这是正确的 - 那么意味着股票指针在范围循环中被“缓存”并且无法覆盖。我会用另一个例子来试试这个。
  • 是的,@NipunTalukdar,我猜range 方法缓存了要循环的变量,然后我认为像我建议的那样覆盖股票行情是不可能的(使用range)-@ 987654322@

标签: go ticker


【解决方案1】:

正如 Nipun Talukdar 提到的,“for”捕获通道并使用相同的引用进行迭代。如果您像这样使用它,它会修复:

playground

package main

import (
    "fmt"
    "log"
    "time"
)

func main() {
    interval := float64(1000)

    ticker := time.NewTicker(time.Duration(interval) * time.Millisecond)
    go func() {
        counter := 1.0
        for {
            select {
            case <-ticker.C:
                log.Println("ticker accelerating to " + fmt.Sprint(interval/counter) + " ms")
                ticker = time.NewTicker(time.Duration(interval/counter) * time.Millisecond)
                counter++
            }
        }
        log.Println("stopped")
    }()
    time.Sleep(5 * time.Second)
    log.Println("stopping ticker")
    ticker.Stop()
}

【讨论】:

  • 感谢您的回答。是的,我也认为使用for-loop 代替range-loop 可以解决这个问题。请注意,在使用for-loop 时,您应该使用select-catcher 管理传入通道。为避免混淆其他开发人员 - 如果您使用正确的 select-cases 编辑 for-code,我将接受您的回答。
  • 当 case 为单一时,select 没有做任何特别的事情。但我明白了,代码也更新了。
  • 我的反馈与“exit”函数有关 - 释放 for-loop 应该返回的 goroutines 中使用的所有变量。即使在单一情况下也会发生这种情况。顺便说一句,它没有被问到,只是想更准确;-)
  • 值得注意的是,当您调用ticker.Stop() 时,您实际上并没有关闭它的通道,因此永远不会到达log.Println("stopped") 行。您应该在完成后保留一个辅助通道来发出信号,然后在代码和辅助通道之间使用select。您还应该在创建新代码之前停止所有旧代码。
  • @hbejgel ticker.C 是一个只接收通道。您不能关闭仅接收频道。拥有完整参考的频道所有者可以关闭它。见golang.org/pkg/time/#Ticker
【解决方案2】:

遵循@fzerorubigd 的答案,但更完整。

如前所述,我们不能在这种情况下使用range,因为range 循环缓存了要删除的变量,然后它不能被覆盖(例如:http://play.golang.org/p/yZvrgURz4o

然后,我们应该使用for-select 组合循环。此后的工作解决方案:

http://play.golang.org/p/3uJrAIhnTQ

package main

import (
    "time"
    "log"
    "fmt"
)

func main() {
    start_interval := float64(1000)
    quit := make(chan bool)

    go func(){
        ticker := time.NewTicker(time.Duration(start_interval) * time.Millisecond)
        counter := 1.0

        for {
            select {
            case <-ticker.C:
                log.Println("ticker accelerating to " + fmt.Sprint(start_interval/counter) + " ms")
                ticker.Stop()
                ticker = time.NewTicker(time.Duration(start_interval/counter) * time.Millisecond)
                counter++
            case <-quit:
                ticker.Stop()
                log.Println("..ticker stopped!")
                return
            }
        }
    }()

    time.Sleep(5 * time.Second)

    log.Println("stopping ticker...")
    quit<-true

    time.Sleep(500 * time.Millisecond) // just to see quit messages
}

【讨论】:

  • 创建新的time.NewTicker 会增加垃圾回收的开销吗?
  • 好问题@chinuy - 即使我正在替换 var ticker,我会说那里需要 GC。如果您查看time.NewTickerHERE 的代码,您会发现它创建了一些本地结构,需要在某个时候被垃圾收集器收集。总结:是的,它增加了 GC 的开销
【解决方案3】:

这段代码怎么样:

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

package main

import (
    "fmt"
    "log"
    "time"
)

func main() {
    startInterval := float64(1000)
    quit := make(chan bool)

    go func() {
        counter := 1.0
        for {
            select {
            case <-time.After(time.Duration(startInterval/counter) * time.Millisecond):
                log.Println("ticker accelerating to " + fmt.Sprint(startInterval/counter) + " ms")
                counter++
            case <-quit:
                log.Println("..ticker stopped!")
                return
            }
        }
    }()

    time.Sleep(5 * time.Second)
    log.Println("stopping ticker...")
    quit <- true
    time.Sleep(500 * time.Millisecond) // just to see quit messages
}

【讨论】:

    【解决方案4】:

    这就是为什么在 go1.15 中创建了ticker.Reset,您不需要创建新的代码更新现有代码的持续时间为ticker.Reset("new duration"),现在您不会有任何缓存问题

    Go playground

    package main
    
    import (
        "fmt"
        "log"
        "time"
    )
    
    func main() {
        interval := float64(1000)
    
        ticker := time.NewTicker(time.Duration(interval) * time.Millisecond)
        go func(){
            counter := 1.0
            for range ticker.C {
                log.Println("ticker accelerating to " + fmt.Sprint(interval/counter) + " ms")
                ticker.Reset(time.Duration(interval/counter) * time.Millisecond)
                counter++
            }
            log.Println("stopped")
        }()
        time.Sleep(5 * time.Second)
        log.Println("stopping ticker")
        ticker.Stop()
    }
    

    您的示例存在缓存问题的原因是,当您使用 *time.ticker 结构重新分配 ticker 变量时,您只需取消原始 *time.ticker 结构与 ticker 变量的链接,但循环仍然趋于原始股票频道,您需要为新的 time.ticker.c 重新设置一个新循环

    【讨论】:

    • 虽然重置确实有效,但 documentation 声明 Reset should be invoked only on stopped or expired timers with drained channels. 所以我不建议重置活动代码。
    • 你把它和time.timer{}.Reset() 混淆在time.tiker{}.Reset() 上没有这个穿着,这里是documentation 给它
    • 值得注意的是ticker.Reset方法可以从go1.15开始。
    • 请解释一下go1.15有什么问题?编译器将使用旧代码
    • @Issac 这不是问题,但值得注意。我使用的是早期版本 (1.14),但它不起作用。
    猜你喜欢
    • 1970-01-01
    • 2016-05-28
    • 2016-12-08
    • 1970-01-01
    • 2021-02-19
    • 2021-01-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多