使用 timer.Reset() 的计时器示例未按描述工作

Timer example using timer.Reset() not working as described

我一直在使用示例来尝试获得我的第一个 "go routine" 运行,而当我得到它时 运行,它不会按照 go 文档的规定工作timer.Reset() 函数。

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

不过,我担心按照文档中的说明进行操作 "must do" 是行不通的。如果我知道做得更好,我会说文档有缺陷,因为它假设缓冲区中有某些东西,但可能没有,但我不知道做得好到足以有信心做出决定,所以我希望一些专家出来有没有可以开导一下。

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

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

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
        }
    }
}

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

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

这没有任何意义,我认为这只是您的 "accident"。

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

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

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

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

Timer.Stop() 记录它 returns true 如果这个调用停止计时器, false 如果计时器已经停止(或过期)。但是你的计时器在创建后就已经停止了,所以 tmr.Stop() returns false 正确,所以你进入 if 并尝试从 tmr.C 接收,但由于计时器已 "long" 停止,因此不会在其通道上发送任何内容,因此这是一个阻塞(永远)操作。

如果您是使用 timer.Stop() 明确停止计时器的人,建议的 "pattern" 耗尽其频道没有任何意义,并且不适用于第二个 Timer.Stop() 打电话。

你没有使用 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")
}

除了 (q.v.), note that the documentation 所说的:

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.

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

中读取,那么这是一个 糟糕的 想法

Reset 文档重复了所有这些,并且顺序错误,因为 ResetStop 之前排序。)

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

  • 没有人在收听频道,现在频道中没有计时器。如果计时器在 调用 t.Stop 之前 已经停止,则通常会出现这种情况。如果计时器已经停止,t.Stop 总是 returns false。
  • 没有人在收听频道,现在频道中有计时器。当计时器为 运行 但 t.Stop 无法阻止它启动时,情况总是如此。在这种情况下,t.Stop return 为假。当计时器 运行 但在 你甚至调用 t.Stop 之前触发 并且因此停止时也是这种情况它自己的,所以 t.Stop 无法阻止它并且 returned false.
  • 其他人正在收听频道。

在最后一种情况下,你什么都不应该做。在第一种情况下,你什么都不应该做。在第二种情况下,您可能希望从通道接收以便将其清除。这就是他们的榜样。

有人可能会争辩说:

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

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

但是,如果 其他人——其他 goroutine——正在或可能正在阅读频道,你根本不应该这样做。尽管调用了 Stop.

,但无法知道谁(您或他们)会得到频道中可能存在的任何计时器滴答声

与此同时,如果您打算继续使用计时器,那么在频道中留下一个计时器滴答声(如果有的话)相对无害。当通道本身被垃圾回收时,它将被垃圾回收。当然,这是否明智取决于您对计时器的处理方式,但在这些情况下,只需调用 t.Stop 并忽略其 return 值就足够了。