Cwww3's Blog

Record what you think

0%

GoQuestion

Question1

time.After 内存泄漏代码

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
package main

import (
"time"
)

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

go func() {
var i = 1
for {
i++
ch <- i
}
}()

for {
select {
case x := <- ch:
println(x)
case <- time.After(3 * time.Minute):
println(time.Now().Unix())
}
}
}

原因分析

在for循环每次select的时候,都会实例化一个一个新的定时器。该定时器在3分钟后,才会被激活,但是激活后已经跟select无引用关系,被gc给清理掉。

换句话说,被遗弃的time.After定时任务还是在时间堆里面,定时任务未到期之前,是不会被gc清理的。

原理验证

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
package main

import (
"time"
"runtime"
"fmt"
)

func main() {
var ms runtime.MemStats
runtime.ReadMemStats(&ms)
fmt.Println("before, have", runtime.NumGoroutine(), "goroutines,", ms.Alloc, "bytes allocated", ms.HeapObjects, "heap object")
for i := 0; i < 1000000; i++ {
time.After(3 * time.Minute)
}
runtime.GC()
runtime.ReadMemStats(&ms)
fmt.Println("after, have", runtime.NumGoroutine(), "goroutines,", ms.Alloc, "bytes allocated", ms.HeapObjects, "heap object")

time.Sleep(10 * time.Second)
runtime.GC()
runtime.ReadMemStats(&ms)
fmt.Println("after 10sec, have", runtime.NumGoroutine(), "goroutines,", ms.Alloc, "bytes allocated", ms.HeapObjects, "heap object")

time.Sleep(3 * time.Minute)
runtime.GC()
runtime.ReadMemStats(&ms)
fmt.Println("after 3min, have", runtime.NumGoroutine(), "goroutines,", ms.Alloc, "bytes allocated", ms.HeapObjects, "heap object")
}

验证结果如下图所示:

image-20210906101432013

从图中可以看出,实例中循环跑完后,创建了3000172个对象,由于每个time定时器设置的为3分钟,在3分钟后,可以看到对象都被GC回收,只剩174个对象,从而验证了,time.After定时器在定时任务到达之前,会一直存在于时间堆中,不会释放资源,直到定时任务时间到达后才会释放资源。

问题解决

综上,在go代码中,在for循环里不要使用select + time.After的组合,可以使用time.NewTimer替代

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
package main

import (
"time"
)

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

go func() {
for {
ch <- 100
}
}()

idleDuration := 3 * time.Minute
idleDelay := time.NewTimer(idleDuration)
defer idleDelay.Stop()

for {
idleDelay.Reset(idleDuration)

select {
case x := <- ch:
println(x)
case <-idleDelay.C:
return
}
}
}

Question2

作用域和短声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
var a = 1
fmt.Println(&a)
if true {
a,_ := get()
fmt.Println(&a)
}
}

func get() (int, error) {
return 1, nil
}

// 输出a的地址不同

原因分析

不同作用域同名变量使用短声明赋值时会产生新的变量进行覆盖

同一作用域同名变量使用短声明赋值时不会产生新的变量,而是对同一个变量进行赋值

问题解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
var a = 1
fmt.Println(&a)
if true {
// 使用临时变量接收,再通过=赋值
tmp,_ := get()
a = tmp
fmt.Println(&a)
}
}

func get() (int, error) {
return 1, nil
}
Donate comment here.