golang中的定时器使用误区,定时器回收,select

golang的定时器虽然使用起来很简单,但是依然又一些细节需要注意的。

首先来看一个例子
func s1() {
	var count int
	for {
		select {
		case <-time.Tick(time.Second * 1):
			fmt.Println("case1")
			count++
			fmt.Println("count--->" , count)
		case <-time.Tick(time.Second * 2) :
			fmt.Println("case2")
			count++
			fmt.Println("count--->" , count)
		}
	}
}

执行结果

case1
count---> 1
case1
count---> 2
case1
count---> 3
case1
count---> 4

可见 case2 永远没有被执行到,问题就出在代码逻辑上,首先看time.Tick方法

func Tick(d Duration) <-chan Time {
	if d <= 0 {
		return nil
	}
	return NewTicker(d).C
}

它每次都会创建一个新的定时器,随着 for 循环进行, select 始终监听两个新创建的定时器,老的定时器被抛弃掉了,也就不会去读取老定时器中的通道。

select 可以同时监听多个通道,谁先到达就先读取谁,如果同时有多个通道有消息到达,那么会随机读取一个通道,其他的通道由于没有被读取,所以数据不会丢失,需要循环调用 select 来读取剩下的通道。

触发定时器

定时器拥有一个长度为 1 的缓冲通道,这保证了系统可以去触发这个定时器,而不用关心是否有 goroutine 在读取这个通道,触发定时器:

func sendTime(c interface{}, seq uintptr) {
	// Non-blocking send of time on c.
	// Used in NewTimer, it cannot block anyway (buffer).
	// Used in NewTicker, dropping sends on the floor is
	// the desired behavior when the reader gets behind,
	// because the sends are periodic.
	select {
	case c.(chan Time) <- Now():
	default:
	}
}

出于安全考虑,go的定时器触发被设置为非阻塞的,这也好理解,如果定时器被触发后,而应用程序始终不来读取通道信息,那么再次触发定时器岂不是要阻塞在那里,这将极大消耗系统资源,因此选择直接跳过。

回收定时器

那么定时器何时被 gc 呢?

Timer

由于 Timer 是一次性的,一旦被触发了,就会被交给 gc ,但是它的通道要等到被读取后才会被关闭。

其次可以手动调用 Stop方法,来提前结束定时器,使其被 gc 。如果Stop调用成功停止了timer,则返回true,如果timer已经被触发过或者已经被停止了,则返回false。同样的,通道并不会被关闭。

Ticker

由于 Ticker 是循环触发的,系统并不会自动停止它,所以,必须要手动调用Stop方法,否则,不光内存得不到释放,cpu也会周期性的被大量占用,即使我们已经不再使用那个Ticker对象了,但是golang底层的定时器还在工作。所以,上面的例子其实是很危险的。必须手动Stop Ticker

可以修改为:

func s2() {
	t1 := time.NewTicker(time.Second * 1)
	t2 := time.NewTicker(time.Second * 2)
	defer t1.Stop()
	defer t2.Stop()

	var count int
	for {
		select {
		case <-t1.C:
			fmt.Println("case1")
			count++
			fmt.Println("count--->" , count)
		case <-t2.C :
			fmt.Println("case2")
			count++
			fmt.Println("count--->" , count)
		}
	}
}

我们经常能看到这样的代码,由于使用的是 Timer ,其实问题不大。

func s3() {
	ch := make(chan struct{}, 1)
	for {
		select {
		case <-ch :
			//
		case <-time.After(time.Second * 1) :
			//
		}
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值