使用计时器的计时器示例.Reset()未按说明工作



我一直在使用示例,试图运行我的第一个"go例程",当我运行它时,它将无法按照go文档中关于计时器的规定工作。Reset()函数。

就我而言,我相信我做这件事的方式很好,因为我实际上不在乎chan缓冲区里有什么。这意味着,如果case _, ok := <-watcher.Events:上发生了任何事情,就触发case <-tmr.C:,然后一切都安静至少一秒钟。原因是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
}
}
}

除了icza所说的(q.v.),请注意,文档中写道:

例如,假设程序尚未从t.C收到:

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

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

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

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

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

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

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()调用。

最新更新