【问题标题】:Timer example using timer.Reset() not working as described使用 timer.Reset() 的计时器示例未按描述工作
【发布时间】:2020-02-25 11:15:20
【问题描述】:

我一直在处理试图让我的第一个“goroutine”运行的示例,当我让它运行时,它不会按照 go 文档中的 timer.Reset() 函数的规定工作。

就我而言,我相信我这样做的方式很好,因为我实际上并不关心 chan 缓冲区中的内容,如果有的话。这意味着如果case _, ok := <-watcher.Events: 上发生任何事情,则触发case <-tmr.C:,然后一切都会安静至少一秒钟。这样做的原因是case _, ok := <-watcher.Events: 可以在微秒之间处理一到几十个事件,我只关心它们都完成并且事情再次稳定下来。

但是,我担心按照文档中所说的“必须做”的方式进行操作是行不通的。如果我知道 go 更好,我会说文档有缺陷,因为它假设缓冲区中有一些东西可能没有,但我不知道 go 足够好来有信心做出这个决定,所以我希望一些专家出来那里可以启发我。

下面是代码。我没有把它放在操场上,因为我必须做一些清理工作(删除对程序其他部分的调用),我不确定我会如何让它对文件系统更改做出反应以显示它的工作。

我已经在代码中清楚地标记了哪些替代方法有效,哪些无效。

func (pm *PluginManager) LoadAndWatchPlugins() error {

  // DOING OTHER STUFF HERE

    fmt.Println(`m1`)

    done := make(chan interface{})
    terminated := make(chan interface{})

    go pm.watchDir(done, terminated, nil)
    fmt.Println(`m2.pre-10`)

    time.Sleep(10 * time.Second)

    fmt.Println(`m3-post-10`)

    go pm.cancelWatchDir(done)
    fmt.Println(`m4`)

    <-terminated
    fmt.Println(`m5`)

    os.Exit(0) // Temporary for testing

    return Err
}

func (pm *PluginManager) cancelWatchDir(done chan interface{}) {
    fmt.Println(`t1`)

    time.Sleep(5 * time.Second)
    fmt.Println()
    fmt.Println(`t2`)

    close(done)
}

func (pm *PluginManager) watchDir(done <-chan interface{}, terminated chan interface{}, strings <-chan string) {

  watcher, err := fsnotify.NewWatcher()
    if err != nil {
        Logger("watchDir::"+err.Error(), `plugins`, Error)
    }

    //err = watcher.Add(pm.pluginDir)
    err = watcher.Add(`/srv/plugins/`)
    if err != nil {
        Logger("watchDir::"+err.Error(), `plugins`, Error)
    }

    var tmr = time.NewTimer(time.Second)
    tmr.Stop()

    defer close(terminated)
    defer watcher.Close()
    defer tmr.Stop()
    for {
        select {
        case <-tmr.C:
            fmt.Println(`UPDATE FIRED`)
            tmr.Stop()

        case _, ok := <-watcher.Events:
            if !ok {
                return
            }

            fmt.Println(`Ticker: STOP`)
            /*
             *  START OF ALTERNATIVES
             *
             *  THIS IS BY EXAMPLE AND STATED THAT IT "MUST BE" AT:
             *      https://golang.org/pkg/time/#Timer.Reset
             *
             *  BUT DOESN'T WORK
             */
            if !tmr.Stop() {
                fmt.Println(`Ticker: CHAN DRAIN`)
                <-tmr.C // STOPS HERE AND GOES NO FURTHER
            }
            /*
             *  BUT IF I JUST DO THIS IT WORKS
             */
            tmr.Stop()
            /*
             *  END OF ALTERNATIVES
             */

            fmt.Println(`Ticker: RESET`)
            tmr.Reset(time.Second)

        case <-done:
            fmt.Println(`DONE TRIGGERED`)
            return
        }
    }
}

【问题讨论】:

    标签: go timer channels routines


    【解决方案1】:

    除了icza said (q.v.),请注意documentation 说:

    例如,假设程序还没有从 t.C 收到:

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

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

    有人可能会说这不是一个很好的例子,因为它假定计时器在您调用t.Stop 时正在运行。但它确实继续提到,如果已经有一些现有的 goroutine 正在或可能正在从 t.C 读取,这是一个的想法。

    Reset 文档重复了所有这些,而且顺序有点错误,因为Reset 排在Stop 之前。)

    基本上,整个区域有点fraught。没有好的通用答案,因为在从t.Stop 返回到您的电话期间至少有三种可能的情况:

    • 没有人在收听频道,并且频道中现在没有计时器滴答声。如果计时器在调用t.Stop 之前已经停止,则通常会出现这种情况。如果计时器已经停止,t.Stop 总是返回 false。
    • 没有人在收听频道,现在频道中有一个计时器滴答声。当计时器正在运行但t.Stop 无法阻止它触发时,总是会出现这种情况。在这种情况下,t.Stop 返回 false。当计时器 运行但在您甚至调用t.Stop 之前触发 时也是这种情况,因此它自行停止,因此t.Stop 无法停止它并返回 false。
    • 其他人正在收听频道。

    在最后一种情况下,你不应该做任何事情。在第一种情况下,你不应该做任何事情。在第二种情况下,您可能希望从通道接收以将其清除。这就是他们的例子。

    有人可能会说:

    if !t.Stop() {
            select {
            case <-t.C:
            default:
            }
    }
    

    是一个更好的例子。它会进行一次非阻塞尝试,如果存在则将消耗计时器滴答声,如果没有计时器滴答声,则不执行任何操作。无论您调用t.Stop 时计时器是否实际运行,这都有效。事实上,如果t.Stop 返回true,它甚至可以工作,尽管在这种情况下,t.Stop 停止了计时器,因此计时器从未设法将计时器滴答声放入通道中。 (因此,如果通道中有数据,则必然是上次清除通道失败时遗留下来的。如果没有此类错误,则无需尝试接收。)

    但是,如果其他人——某个其他 goroutine——正在或可能正在读取通道,那么你根本不应该这样做。尽管调用了Stop,但无法知道谁(您或他们)将获得频道中可能存在的任何计时器滴答声。

    同时,如果您打算进一步使用计时器,那么在频道中留下一个计时器滴答(如果有的话)是相对无害的。当通道本身被垃圾收集时,它将被垃圾收集。当然,这是否合理取决于您对计时器所做的事情,但在这些情况下,只需调用 t.Stop 并忽略其返回值即可。

    【讨论】:

    • @Debashish:因为通道是同步点,而t.C 有一个缓冲区条目,所以可能性有限:要么通道中有一个刻度,要么有没有;任何一个人——一些围棋程序;我喜欢将它们视为人或地鼠——*被*阻塞在接收中,如果不是;当你调用t.Stop 时,发送滴答的goroutine(它是一个系统goroutine)要么已经开始发送,要么没有。
    • 所以:假设 goroutine A 在接收 (&lt;-t.C) 时被阻塞,并且通道中现在没有刻度。一些其他的 goroutine B——它一定是其他的; A 在接收中被阻止 - 呼叫 t.Stop()。让我们调用系统的 goroutine,如果我们不先停止它,它将发送一个刻度,S。有两种情况:当 B 调用 t.Stop() 时,S 没有也不会在 t.C 中添加刻度,或者, S 已经或将在t.C 中打勾。如果 S 不会在 B 调用 t.Stop() 时打勾,那么 A 现在会卡住,因为不会显示任何勾号。
    • 虽然 B 可以尝试弥补这一点,但最好以不会卡住的方式编写 A:即,它具有 条件接收,select
    • 让我们为 goroutine 分配名称,以便于讨论它们。 Goroutine A 位于&lt;-t.C 中,并且已经持续了 2 秒。 Goroutine B 现在调用t.Reset(10*time.Second)。 Goroutine S(将在 3 秒后发送一个滴答声)还没有尝试发送一个滴答声; B将剩余时间改为10秒;所以 S 现在最终将在 10 秒内发送一个滴答声。 A 现在会在大约 12 秒后唤醒一段时间,是的。
    • 这种代码通常很棘手。如果 A 已经等待了 5 秒,而不是 2 秒,并且 S 已经 开始并且 正在 在发送滴答声的过程中,当 B打电话给t.Reset(10)? S 将在 10 秒后发送另一个刻度。在不知道 A 和 B 的其余代码的情况下,我们无法判断接下来会发生什么。
    【解决方案2】:

    您创建了一个计时器并立即停止它:

    var tmr = time.NewTimer(time.Second)
    tmr.Stop()
    

    这没有任何意义,我认为这只是你的“意外”。

    但更进一步,在你的循环中:

        case _, ok := <-watcher.Events:
    

    发生这种情况时,您声称这不起作用:

            if !tmr.Stop() {
                fmt.Println(`Ticker: CHAN DRAIN`)
                <-tmr.C // STOPS HERE AND GOES NO FURTHER
            }
    

    Timer.Stop() 记录了如果此调用停止计时器,它将返回 true,如果计时器已经停止(或过期),则返回 false。但是你的定时器在创建之后就已经停止了,所以tmr.Stop() 正确地返回了false,所以你进入if 并尝试从tmr.C 接收,但是由于定时器是“长时间”停止的,所以什么也没有将在其通道上发送,因此这是一个阻塞(永久)操作。

    如果您是使用timer.Stop() 明确停止计时器的人,则推荐的“模式”来耗尽其通道没有任何意义,并且不适用于第二次Timer.Stop() 调用。

    【讨论】:

    • 立即停止计时器并非偶然。我本来希望创建它,但还没有开始滴答作响,因此它只开始在来自另一个频道的事件上滴答作响,但我不知道该怎么做。如果我只是将 var 设置为 *Timer 类型而不是启动计时器,那么选择会卡住,所以我必须启动计时器然后立即停止它。
    【解决方案3】:

    你错过了使用 time.Timer。

    试试这样的简单方法

    package main
    
    import (
        "fmt"
        "log"
        "time"
    )
    
    func main() {
        timer := time.NewTimer(time.Second)
        watcher := make(chan bool)
        done := make(chan bool)
        evs := make(chan bool)
        go func() {
            trigger := false
            for {
                select {
                case <-timer.C:
                    if trigger {
                        trigger = false
                        evs <- true
                    }
                    timer.Reset(time.Second)
    
                case _, ok := <-watcher:
                    if !ok {
                        return
                    }
                    trigger = true
    
                case <-done:
                    fmt.Println(`DONE TRIGGERED`)
                    return
                }
            }
        }()
        go func() {
            for e := range evs {
                log.Println("e", e)
            }
        }()
        // simulate multiple events
        watcher <- true
        watcher <- true
        watcher <- true
        watcher <- true
        <-time.After(time.Second + time.Millisecond*100)
        watcher <- true
        watcher <- true
        watcher <- true
            <-time.After(time.Second + time.Millisecond*100)
    
        fmt.Println("Hello, playground")
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-12-26
      • 2021-01-17
      • 1970-01-01
      • 2013-08-04
      • 2018-09-26
      • 2017-03-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多