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 时退出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值