
回复
上面这道题在不执行resp.Body.Close()
的情况下,泄漏了吗?如果泄漏,泄漏了多少个goroutine
?
resp.Body.Close()
,泄漏是一定的。但是泄漏的goroutine
个数就让我迷糊了。由于执行了6遍,每次泄漏一个读和写goroutine,就是12个goroutine,加上main函数
本身也是一个goroutine
,所以答案是13.golang
的http
包。
http.Get
默认使用DefaultTransport
管理连接。DefaultTransport
是干嘛的呢?
DefaultTransport
的作用是根据需要建立网络连接并缓存它们以供后续调用重用。DefaultTransport
什么时候会建立连接呢?接着上面的代码堆栈往下翻
读goroutine
和写goroutine
。这就是为什么一次http.Get()
会泄漏两个goroutine
的来源。close
close
会泄漏呢?读goroutine
的readLoop()
代码里
readLoop
就是一个死循环,只要alive
为true
,goroutine
就会一直存在select
里面是goroutine
有可能退出的场景:body
被读取完毕或body
关闭request
主动cancel
request
的context Done
状态true
persistConn
关闭其中第一个 body
被读取完或关闭这个 case
:
bodyEOF
来源于到一个通道 waitForBodyRead
,这个字段的 true
和 false
直接决定了 alive
变量的值(alive=true
那读goroutine
继续活着,循环,否则退出goroutine
)。
earlyCloseFn
,waitForBodyRead
通道输入的是false
,alive
也会是false
,那readLoop()
这个goroutine
就会退出。fn
,其中包括正常情况下body
读完数据抛出io.EOF
时的case
,waitForBodyRead
通道输入的是true
,那alive
会是true
,那么readLoop()
这个goroutine
就不会退出,同时还顺便执行了tryPutIdleConn(trace)
。
tryPutIdleConn
将pconn
添加到等待新请求的空闲持久连接列表中,也就是之前说的连接会复用。fn
和 earlyCloseFn
呢?
resp.Body.Close()
,在里面会执行earlyCloseFn
,也就是此时readLoop()
里的waitForBodyRead
通道输入的是false
,alive
也会是false
,那readLoop()
这个goroutine
就会退出,goroutine
不会泄露。
read
,其实就是bodyEOFSignal
里的
body
里的内容。ioutil.ReadAll()
,在读完body
的内容时会执行fn
,也就是此时readLoop()
里的waitForBodyRead
通道输入的是true
,alive
也会是true
,那readLoop()
这个goroutine
就不会退出,goroutine
会泄露,然后执行tryPutIdleConn(trace)
把连接放回池子里复用。6
次循环,而且每次都没有执行Body.Close()
,就是因为执行了ioutil.ReadAll()
把内容都读出来了,连接得以复用,因此只泄漏了一个读goroutine
和一个写goroutine
,最后加上main goroutine
,所以答案就是3个goroutine
。ioutil.ReadAll()
,但如果此时忘了resp.Body.Close()
,确实会导致泄漏。但如果你调用的域名一直是同一个的话,那么只会泄漏一个读goroutine
和一个写goroutine
,这就是为什么代码明明不规范但却看不到明显内存泄漏的原因。文章转载自公众号:小白debug