2

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

就我而言,我相信我这样做的方式很好,因为我实际上并不关心 chan 缓冲区中的内容,如果有的话。case <-tmr.C:所有这一切都是为了在发生任何事情时触发case _, ok := <-watcher.Events:,然后一切都会安静至少一秒钟。这样做的原因是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
        }
    }
}
4

3 回答 3

3

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

例如,假设程序还没有从 tC 收到:

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

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

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

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

从本质上讲,整个区域有点令人担忧。没有好的通用答案,因为在返回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并忽略它的返回值就足够了。

于 2019-10-30T18:52:19.507 回答
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()调用。

于 2019-10-30T08:48:18.220 回答
-1

你错过了使用时间。定时器。

尝试像这样简单的事情

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")
}
于 2019-10-30T08:51:59.290 回答