time.After()
1. time.After()官方解释
刚开始学习时,time包里sleep最常用,在学习context时,突然看到一个time.after,首先是time包的定义:
// After waits for the duration to elapse and then sends the current time
// on the returned channel.
// It is equivalent to NewTimer(d).C.
// The underlying Timer is not recovered by the garbage collector
// until the timer fires. If efficiency is a concern, use NewTimer
// instead and call Timer.Stop if the timer is no longer needed.
func After(d Duration) <-chan Time {
return NewTimer(d).C
}
英文看不懂没关系,来这里翻译一下,这个软件翻译的太准确了!
2. time.After()代码示例
func main() {
tchan := time.After(time.Second * 3)
fmt.Printf("tchan type=%T\n", tchan)
fmt.Println("mark 1")
for i := 0; i < 10; i++ {
fmt.Print(i)
}
fmt.Println()
fmt.Println("tchan,", <-tchan)
fmt.Println("mark 2")
out
tchan type=<-chan time.Time
mark 1
0123456789
tchan, 2022-08-11 21:47:29.7323266 +0800 CST m=+3.002611601
mark 2
这个例子比较简单,来分析一下
- 首先调用time.After(duration),此函数马上返回一个time.Time类型的
Chan
,不阻塞。注意如果是sleep()
,就会阻塞了,就不会往下走了,直到时间到了。而After()不会阻塞,后面你该做什么做什么,不影响。 - 到了duration时间后,自动塞一个
当前时间
进去。 - 写了简单的测试的示例代码,放在了这里。go-basic-exercises/timerAfter_test.go
3. AfterFunc()
在学习了After()
后,那么在它的基础上,还有一个AfterFunc()
,这个可以指定函数在多长时间执行,也不影响做其他事情。而且可以使用stop() 取消执行。这也是一个很好的设计,比如我们可以设置了一个功能函数,定点到10分钟后执行,但是如果发现发生了异常的事情,或者是发现满足了不需要执行这个函数的条件,就可以stop()掉。就如同抗日片中播放的,计划晚上六点攻打,如果检测到环境不对,那就取消攻打计划。
func TestAfter(t *testing.T) {
timer := time.AfterFunc(time.Second*30, func() {
fmt.Println("30s到了哦")
})
time.Sleep(time.Second * 20)
timer.Stop()
time.Sleep(time.Minute)
}
有意思的是,我在调试程序中,没有设置阻塞时间,程序一下子就运行完成了,如果不设置延时,主goroutine结束了,它也结束了。举一个例子,我设置下午两点的闹钟,这是不是一下子就设置完了,这可以当成函数的声明,只是告诉你到了这个点要做这个事情,但是如果你不延时,阻塞主goroutine,就好像你让手机两点叫你起床,但是你又没等到那个时间把手机关机了,这个时候当然不会执行这个动作了。下面是更形象的例子:
var notify bool
func main() {
timer := time.AfterFunc(30*time.Millisecond, func() {
fmt.Println("30ms到了哦,我开始执行啦...")
})
fmt.Println("等待到时执行函数,我去忙其他事情...")
time.Sleep(20 * time.Millisecond)
notify = true
if notify {
timer.Stop()
fmt.Println("收到通知,情况有变,任务取消")
}
time.Sleep(50 * time.Millisecond)
}
out
等待到时执行函数,我去忙其他事情...
收到通知,情况有变,任务取消
4. 总结
- sleep() 和 after() 底层,应该实现是差不多的,有时间可以分析一下它们源码,看看实现方式的差异。after()在Go中确实是一个很好的设计。sleep() 会阻塞,而after() 不会影响后面的运行。
- 在异步场景中,尤其是context等,基本上都是返回 channel,然后在一个必要的地方用 select case 来监听。