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

use*_*087 4 timer channel go routines

我一直在尝试让我的第一个“go 例程”运行的示例,当我让它运行时,它不会按照 go 文档中规定的带有 timer.Reset() 函数的方式工作。

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

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

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

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

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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

tor*_*rek 6

除了icza 所说的(qv)之外,请注意文档中说:

\n\n
\n

例如,假设程序尚未从 tC 接收到:

\n\n
if !t.Stop() {\n        <-t.C\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n
\n\n

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

\n\n

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

\n\n

本质上,整个地区都有点令人担忧。没有好的通用答案,因为在返回t.Stop呼叫期间至少存在三种可能的情况:

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

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

\n\n

有人可能会争辩说:

\n\n
if !t.Stop() {\n        select {\n        case <-t.C:\n        default:\n        }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

但是,如果其他人\xe2\x80\x94 或其他 goroutine\xe2\x80\x94 正在读取通道,则您根本不应该执行任何操作。尽管调用了 ,但无法知道谁(您或他们)将获得通道中可能存在的任何计时器滴答声Stop

\n\n

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

\n