Golang 定时器与 time.After() 结合使用踩坑记录

功能描述: 在 goroutine 定时执行一些内容比如: 打印 hello world,然后五分钟或者十分钟后退出 goroutin。

功能实现

func main() {

	go func() {
		ticker := time.NewTicker(time.Second * 1)
		for {
			select {
			case <-ticker.C:
				fmt.Println("hello world")
			case <-time.After(time.Second * 10):
				fmt.Println("exit")
				return
			}
		}
	}()

	time.Sleep(time.Second * 15)
	fmt.Println("main func")
}

每个 1s 打印一下 hello world 然后在 10s 后退出 goroutine,15s 后退出程序。我估计大多数同学都会写成这样,输入如下

hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
main func

从输入内容可以看出,程序根本没有打印 exit,也证明了 goroutine 不是由 time.After() 退出,而是由于主协程(main) sleep 结束之后退出。

思考问题,为什么 time.After() 没有像我们想象的一样退出 goroutine?
首先看一下底层实现

func After(d Duration) <-chan Time {
	return NewTimer(d).C
}

func NewTimer(d Duration) *Timer {
	c := make(chan Time, 1)
	t := &Timer{
		C: c,
		r: runtimeTimer{
			when: when(d),
			f:    sendTime,
			arg:  c,
		},
	}
	startTimer(&t.r)
	return t
}

After() 函数接受一个时长 d,然后 After() 等待 d 时长,等待时间到后,将等待完成时所处时间点写入到 channel 中并返回这个只读 channel。

看到这里就明白了为什么我们使用没有生效,通过底层可以看出, NewTimer(d).C 每次都是 return 了一个新的对象。并且我们是在 for 循环中定时执行 select,也就相当于每一次执行 select 我们都重新创建(实例化)了新的 time.After()。换句话说,每一次执行 select time.After() 都会重新开始计时。

现在知道了问题,我们只需要不在 for 循环中初始化 time.After() 即可。修改一下实现

func main() {

	go func() {
		// 注释内容也可以使用
		// idleDuration := time.NewTimer(time.Second * 10).C
		idleDuration := time.After(time.Second * 10)
		ticker := time.NewTicker(time.Second * 1)

		for {
			select {
			case <-ticker.C:
				fmt.Println("hello world")
			case <-idleDuration:
				fmt.Println("exit")
				return
			}
		}
	}()

	time.Sleep(time.Second * 15)
	fmt.Println("main func")
}

看一下输出:

hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
exit
main func

可以看到程序在打印第十遍 hello world 时退出。

你可能感兴趣的:(golang,学习路线,golang,time,NewTicker,time.After,select)