我正在尝试建立一个每小时运行一次的服务例程。在我看来,这两个都很容易。要在小时内运行例程,可以使用time.AfterFunc()
,首先计算到小时数为止的剩余时间。为了每小时运行一次例程,我可以使用time.NewTicker()
。
但是,只有在传递给NewTicker
的函数被触发后,我才能努力找出如何启动AfterFunc()
。
我的main()
函数看起来像这样:
func main() {
fmt.Println("starting up")
// Here I'm setting up all kinds of HTTP listeners and gRPC listeners, none
// of which is important, save to mention that the app has more happening
// than just this service routine.
// Calculate duration until next hour and call time.AfterFunc()
// For the purposes of this exercise I'm just using 5 seconds so as not to
// have to wait increments of hours to see the results
time.AfterFunc(time.Second * 5, func() {
fmt.Println("AfterFunc")
})
// Set up a ticker to run every hour. Again, for the purposes of this
// exercise I'm ticking every 2 seconds just to see some results
t := time.NewTicker(time.Second * 2)
defer t.Stop()
go func() {
for now := range t.C {
fmt.Println("Ticker")
}
}()
// Block until termination signal is received
osSignals := make(chan os.Signal, 1)
signal.Notify(osSignals, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
<-osSignals
fmt.Println("exiting gracefully")
}
当然time.AfterFunc()
正在阻塞,而我的Ticker
的有效负载被故意放入go例程中,因此它也不会阻塞。这样,我的HTTP和gRPC侦听器可以继续侦听,但也允许main()
末尾的代码块根据来自操作系统的终止信号而正常退出。但是现在明显的缺点是,Ticker
会立即启动,并在函数传递给AfterFunc()
之前触发两次(间隔2秒)触发。输出看起来像这样:
Ticker
Ticker
AfterFunc
Ticker
Ticker
Ticker
etc.
我当然想要的是:
AfterFunc
Ticker
Ticker
Ticker
Ticker
Ticker
etc.
以下内容也不起作用,我不确定为什么。它会打印AfterFunc,但不会触发代码。
time.AfterFunc(time.Second * 5, func() {
fmt.Println("AfterFunc")
t := time.NewTicker(time.Second * 2)
defer t.Stop()
go func() {
for now := range t.C {
fmt.Println("Ticker")
}
}()
})
答案 0 :(得分:1)
The Go Programming Language Specification
程序执行从初始化主程序包开始,然后 调用函数main。当该函数调用返回时, 程序退出。它不等待其他(非主)goroutine进入 完成。
time.AfterFunc(time.Second * 5, func() {
fmt.Println("AfterFunc")
t := time.NewTicker(time.Second * 2)
defer t.Stop()
go func() {
for now := range t.C {
fmt.Println("Ticker")
}
}()
})
defer t.Stop()
停止了股票行情。
您不是在等待goroutine运行。
答案 1 :(得分:0)
天哪,我在发布后不久就发现了这一点,尽管我仍然认为我的解决方案缺乏优雅之处。
@peterSO指出,传递给AfterFunc()
的函数在用Ticker
创建后执行并停止defer t.Stop()
片刻。
对我来说,解决方案是在调用t
之前定义AfterFunc()
变量,以使其在AfterFunc()
有效负载函数之外具有作用域,然后在结束时将其停止我的main()
函数。这是新的main()
函数:
func main() {
fmt.Println("starting up")
var t *time.Ticker
time.AfterFunc(time.Second * 5, func() {
fmt.Println("AfterFunc")
t = time.NewTicker(time.Second * 2)
go func() {
for now := range t.C {
fmt.Println("Ticker")
}
}()
})
//defer t.Stop()
// Block until termination signal is received
osSignals := make(chan os.Signal, 1)
signal.Notify(osSignals, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
<-osSignals
t.Stop()
logger.Info("exiting gracefully")
}
奇怪的是,尽管注释defer t.Stop()
导致应用程序因终止信号而关闭,但会导致紧急情况(无效的内存地址或nil指针取消引用)。如果我在代码末尾像未注释的t.Stop()
中那样停止它,则它将按预期工作。不知道为什么会这样。
答案 2 :(得分:-1)
time.AfterFunc(time.Second*5, func() {
fmt.Println("AfterFunc")
t := time.NewTicker(time.Second * 2)
defer t.Stop()
for range t.C {
fmt.Println("Ticker")
}
})
它产生您需要的输出:
starting up
AfterFunc
Ticker
Ticker
Ticker
Ticker
Ticker