Golang| golang中为什么resp.body要关闭
本文介绍在golang的标准库net/http中,为什么请求之后,要主动关闭resp.body、以及不关闭resp.body有什么影响。
问题描述
在golang中,我们使用http标准库发起一个http请求,一般会使用如下的方式:
resp, err := http.Get("http://www.baidu.com")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(body))
在上面的代码中,我们使用http.Get发起一个http请求,然后使用ioutil.ReadAll读取resp.Body的内容,最后关闭resp.Body。
在学习其他编程语言,如python,我们一般不需要进行类似的操作,这是为什么呢?
问题分析
阅读源码
要回答这个问题,我们首先要知道net/http包在做请求的时候,背后都做了哪些事情,所以要阅读net/http包的源码。
getConn:获取一个连接,或者重新创建连接

queueForDial: 把连接创建请求(后文简称w,一个经过初始化的wantConn结构体)放入到dialer的队列中等待被使用
一旦满足以下两种条件:
- 没有限制MaxConnsPerHost
- 还没有达到MaxConnsPerHost

就会通过dialConnFor函数为w创建一个真正的往目标地址的连接,dialConnFor函数创建连接调用了dialConn函数。
而dialConn函数是最关键的,在dialConn函数的最后,拉起了两个goroutine:
- readloop
- writeloop
我们这里分析下这两个goroutine的作用以及退出条件。
- readloop
通过调用pc.readResponse函数接收response。
我们省略掉一些不重要的代码,只看关键的部分: readloop内部有一个for循环,循环的退出条件是:alive == false,而alive赋值为false的时机是:
- resp.Close为true
- rc.req.Close为true
- resp.StatusCode == 199(https://http.dev/199)
- bodyWritable为true
其他三点我们先忽略,重点关注resp.Close条件。这就是问题的答案,如果不执行resp.Body.Close,那么resp.Close永远为false,所以readloop永远不会退出,就会在程序生命周期中永远保留一个goroutine和文件句柄。

- writeloop
writeloop的作用是往目标地址写入请求数据,并且进行一些写入的错误处理。
它内部是一个阻塞式的for-select控制结构,退出的条件是:
- pc.closech能够读到数据
- 写入数据时出错

所以,如果我们在使用http.Get发起请求时,没有执行resp.Body.Close,那么readloop和writeloop就会一直保留,直到程序退出。
问题总结
在net/http包中,如果我们在发起请求时没有执行resp.Body.Close,那么就会导致readloop和writeloop这两个goroutine一直保留,直到程序退出。所以,为了防止goroutine泄露,我们需要在发起请求后,及时执行resp.Body.Close。