ioutil.ReadAll 导致 goroutine 泄漏

ioutil.ReadAll leads to goroutine leak

为什么我在终止前有多个 goroutine,即使我关闭了 resp.body,而我只使用了阻塞调用?如果我不使用 resp.Body 它只会以一个 goroutine 终止。

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "runtime"
    "time"
)

func fetch() {
    client := http.Client{Timeout: time.Second * 10}
    url := "http://example.com"
    req, err := http.NewRequest("POST", url, nil)
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    _, err = ioutil.ReadAll(resp.Body)
    if err != nil {
        panic(err)
    }
}

func main() {
    fmt.Println("#Goroutines:", runtime.NumGoroutine())
    fetch()
    // runtime.GC()
    time.Sleep(time.Second * 5)
    fmt.Println("#Goroutines:", runtime.NumGoroutine())
}

输出:

#Goroutines: 1
#Goroutines: 3

默认的 http 传输 maintains a connection pool

DefaultTransport is the default implementation of Transport and is used by DefaultClient. It establishes network connections as needed and caches them for reuse by subsequent calls.

每个连接至少由一个协程管理。但这不是泄漏,您只是不耐烦。如果你等待的时间足够长,你会看到连接最终关闭并且 goroutine 消失了。默认空闲超时为 90 秒。

如果您想尽快关闭连接,请将 http.Request.Close or http.Transport.DisableKeepAlives 中的任何一个设置为 true。