参考:
先来看几道题,想一想最终的输出结果是多少呢?
package main
import (
"fmt"
"io/ioutil"
"net/http"
"runtime"
)
// 测试1:不执行resp.Body.Close(),也不执行ioutil.ReadAll(resp.Body)
func testFun1() {
for i := 0; i < 5; i++ {
_, err := http.Get("https://www.baidu.com")
if err != nil {
fmt.Println("testFun1 http.Get err: ", err)
return
}
}
time.Sleep(time.Second * 1)
fmt.Println("testFun1() 当前goroutine数量=", runtime.NumGoroutine())
}
// 测试2:执行resp.Body.Close(),不执行ioutil.ReadAll(resp.Body)
func testFun2() {
for i := 0; i < 5; i++ {
resp, err := http.Get("https://www.baidu.com")
if err != nil {
fmt.Println("testFun2 http.Get err: ", err)
return
}
resp.Body.Close() // 执行resp.Body.Close()
}
// Close()过程需要一定时间,如果直接输出goroutine数量,可能出现连接还未完全回收的情况,结果有时为1有时为3
// 因此,为了结果的准确性,我们这里休眠等待1秒,使得连接完全被回收。
time.Sleep(time.Second * 1)
fmt.Println("testFun2() 当前goroutine数量=", runtime.NumGoroutine())
}
// 测试3:不执行resp.Body.Close(),执行ioutil.ReadAll(resp.Body)
func testFun3() {
for i := 0; i < 5; i++ {
resp, err := http.Get("https://www.baidu.com")
if err != nil {
fmt.Println("testFun3 http.Get err: ", err)
return
}
_, _ = ioutil.ReadAll(resp.Body) // 执行ioutil.ReadAll(resp.Body)
}
time.Sleep(time.Second * 1)
fmt.Println("testFun3() 当前goroutine数量=", runtime.NumGoroutine())
}
// 测试4:执行resp.Body.Close(),也执行ioutil.ReadAll(resp.Body)
func testFun4() {
for i := 0; i < 5; i++ {
resp, err := http.Get("https://www.baidu.com")
if err != nil {
fmt.Println("testFun4 http.Get err: ", err)
return
}
_, _ = ioutil.ReadAll(resp.Body) // 执行ioutil.ReadAll(resp.Body)
resp.Body.Close() // 执行resp.Body.Close()
}
time.Sleep(time.Second * 1)
fmt.Println("testFun4() 当前goroutine数量=", runtime.NumGoroutine())
}
func main() {
testFun1()
testFun2()
testFun3()
testFun4()
}
答案:
- testFun1() 当前goroutine数量= 11
- testFun2() 当前goroutine数量= 1
- testFun3() 当前goroutine数量= 3
- testFun4() 当前goroutine数量= 3
注意:
resp.Body.Close()time.Sleep(time.Second * 1)
resp.Body.Close()ioutil.ReadAll(resp.Body)
解析:
resp.Body.Close()
另外,稍微了解 go net/http 包的同学,都知道 每次执行http的 Get/Post 请求时,底层都会创建两个协程,分别处理 写请求/读响应 这两个事件。具体底层逻辑后面会提到 …
所以你可以简单的理解为:执行一条http请求时,go内部会创建两个协程。
接下来,针对每一个示例做分析:
resp.Body.Close()ioutil.ReadAll(resp.Body)resp.Body.Close()ioutil.ReadAll(resp.Body)resp.Body.Close()resp.Body.Close()ioutil.ReadAll(resp.Body)ioutil.ReadAll(resp.Body)resp.Body.Close()ioutil.ReadAll(resp.Body)ioutil.ReadAll(resp.Body)resp.Body.Close()
源码解析:
通过跟踪 go net/http 包的源码,得到其调用链路的流程图:
可以发现每次新建立一个http请求,最终底层实际上都会创建两个新的协程(写请求/读响应)
go pconn.readLoop()go pconn.writeLoop()
readLoop()writeLoop()
alivewaitForBodyReadwaitForBodyRead <- isEOFbodyEOF := <-waitForBodyReadwaitForBodyReadwaitForBodyRead <- falsepc.close(closeErr)writeLoop
waitForBodyRead
总结:
ioutil.ReadAll(resp.Body)resp.Body.Close()resp.Body.Close()