前言
函数原型为:
func AfterFunc(d Duration, f func()) *Timer
Go 的 time.AfterFunc 的作用是等待指定的时间间隔,然后在它自己的 goroutine 中调用 f。
现在有一个问题,我明明调用了 AfterFunc,但是它还没调用我指定的函数,程序就退出了。程序如下所示:
package main
import (
"fmt"
"time"
)
func main() {
time.AfterFunc(5*time.Second, func() {
fmt.Println("5s passed")
})
}
这段代码什么都没打印就结束了。
问题解决
上面的问题看起来像是 main goroutine 退出了,程序直接退出,还没来得及调用我们指定的函数。
下面就通过源码来看看是不是我们猜测的这样,AfterFunc 的源码如下:
func AfterFunc(d Duration, f func()) *Timer {
t := &Timer{
r: runtimeTimer{
when: when(d),
f: goFunc,
arg: f,
},
}
startTimer(&t.r)
return t
}
上面的代码很简单,先构造一个定时器,然后启动定时器。定时器的内容也很好理解,在 when: when(d) 时间,调用 f: goFunc 函数,函数的参数是 arg: f。
when(d) 的值是什么呢?
func when(d Duration) int64 {
if d <= 0 {
return runtimeNano()
}
t := runtimeNano() + int64(d)
if t < 0 {
t = 1<<63 - 1 // math.MaxInt64
}
return t
}
可以看到 when 就是当前时间,再加上我们指定的时间间隔。
那 goFunc 又是什么呢?
func goFunc(arg any, seq uintptr) {
go arg.(func())()
}
这个函数的作用就是创建一个 goroutine,在 goroutine 内运行 arg 函数。
arg 就是我们传给 AfterFunc 要运行的函数,先使用类型断言将其转换成函数,然后再运行它。
于是,问题就解决了,就是因为 main goroutine 先退出了,导致程序直接退出,还没来得及执行我们指定的打印函数。下面给出两种解决方案:
使用 select 替换 AfterFunc:
func main() {
select {
case <-time.After(5 * time.Second):
func() {
fmt.Println("5s passed")
}()
}
}
使用 WaitGroup 等待其他 goroutine 结束:
func main() {
var wg sync.WaitGroup
wg.Add(1)
time.AfterFunc(5*time.Second, func() {
defer wg.Done()
fmt.Println("5s passed")
})
wg.Wait()
}