慎用<-time.After(),容易导致内存泄漏

问题代码

顺带说明下select 监听是如何工作的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import (
"fmt"
"time"
)

func main() {
ch := make(chan int)

//go start
go func() {
for {
timerC := time.After(2 * time.Second)
//timerC 每次都是重新创建的,什么意思呢?简单说来,当 select 成功监听 ch 并进入它的处理分支,下次循环 timerC 重新创建了,时间肯定就重置了。
select {
//如果有多个 case 都可以运行,select 会随机公平选择出一个执行。其余的则不会执行
case num := <-ch:
fmt.Println("get num is", num)
case <-timerC:
//等价于 case <-time.After(2 * time.Second)
fmt.Println("time's up!!!")
//done<-true
}
}
}()

for i := 1; i < 5; i++ {
ch <- i
time.Sleep(time.Second * 2)
}
}

问题说明

以上代码会导致内存泄漏,其罪魁祸首是<-time.After(),在官方文档中有此说明:如果定时器没有到达定时时间,gc 就不会启动垃圾回收。标准库文档中有说明:

The underlying Timer is not recovered by the garbage collector until the timer fires

解决方法

在不使用 time.After 来实现超时的前提下,可通过创建 timer 配合 reset 来实现超时机制,具体代码示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

....

go func(){
idleDuration := 2 * time.Second
idleDelay := time.NewTimer(idleDuration)
defer idleDelay.Stop()
for {
idleDelay.Reset(idleDuration)
select {
case num := <-ch:
fmt.Println("get num is", num)
case <-idleDelay.C:
fmt.Println("time's up!!!")
//done<-ture
}
}
}()

....

参考:

  • studygolang.com/articles/22617
-------------本文结束感谢您的阅读-------------