goroutine作为Go中开发语言中的一大利器,在高并发中发挥着无法忽略的作用。但东西虽好,真正做到用好还是有一些要注意的地方,特别是对于刚刚接触这门开发语言的新手来说。
出现的问题
先看一段代码:
package main import ( "fmt" "math/rand" "runtime" "time" ) func query() int { n := rand.Intn(100) time.Sleep(time.Duration(n) * time.Millisecond) return n } func queryAll() int { ch := make(chan int) go func() { ch <- query() }() go func() { ch <- query() }() go func() { ch <- query() }() // <-ch // <-ch return <-ch } func main() { for i := 0; i < 4; i++ { queryAll() fmt.Printf("#goroutines: %d\n", runtime.NumGoroutine()) } }
运行结果
#goroutines: 3 #goroutines: 5 #goroutines: 7 #goroutines: 9
这里发现goroutine的数量一直在增涨,按理说这里的值应该一直是 1 才对的呀(只有一个Main 函数的主goroutine)。其实这里发生了goroutine泄漏的问题。
主要问题发生在 queryAll() 函数里,这个函数在goroutine里往ch里连续三次写入了值,由于这里是无缓冲的ch,所以在写入值的时候,要有在ch有接收者时才可以写入成功,也就是说在从接收者从ch中获取值之前, 前面三个ch<-query() 一直处于阻塞的状态。当执行到queryAll()函数的 return语句 时,ch接收者获取一个值(意思是说三个ch<-query() 中执行最快的那个goroutine写值到ch成功了,还剩下两个执行慢的 ch<-query() 处于阻塞)并返回给调用主函数时,仍有两个ch处于浪费的状态。
在Main函数中对于for循环
第一次:goroutine的总数量为 1个主goroutine + 2个浪费的goroutine = 3
第二次:3 + 再个浪费的2个goroutine = 5
第三次:5 + 再个浪费的2个goroutine = 7
第三次:7 + 再个浪费的2个goroutine = 9
正好是程序的输出结果。
好了,问题我们知道怎么回事了,剩下的就是怎么解决了
解决方案:
可以看到,主要是ch写入值次数与读取的值的次数不一致导致的有ch一直处于阻塞浪费的状态,我们所以我们只要保存写与读的次数完全一样就可以了。
这里我们把上面queryAll() 函数代码注释掉的 <-ch 两行取消掉,再执行就正常了,输出内容如下:
#goroutines: 1 #goroutines: 1 #goroutines: 1 #goroutines: 1
对于goroutine的数量只有一个,也必须有一个,因为Main()函数也是一个goroutine。(http://docs.studygolang.com/src/runtime/proc.go?s=102731:102737#L3607 https://www.kancloud.cn/mutouzhang/go/596824)
当然对于解决goroutine的方法不是仅仅这一种,也可以利用context来解决,参考:https://www.cnblogs.com/chenqionghe/p/9769351.html。
提醒:
垃圾收集器不会收集以下形式的goroutines:
go func() { // <操作会在这里永久阻塞> }() // Do work
这个goroutine将一直存在,直到整个程序退出。
其它:
1.如何定义Golang中的goroutine中的泄漏概念? 有没有相应的泄漏检测工具?https://blog.51cto.com/13778063/2152654
参考:
https://studygolang.com/articles/12495
https://blog.csdn.net/qq_16205285/article/details/90113419
https://segmentfault.com/q/1010000007735676