<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                ## 問題 ~~~go package main import ( "fmt" "io/ioutil" "net/http" "runtime" ) func main() { num := 6 for index := 0; index < num; index++ { resp, _ := http.Get("https://www.baidu.com") _, _ = ioutil.ReadAll(resp.Body) } fmt.Printf("此時goroutine個數= %d\n", runtime.NumGoroutine()) ~~~ } 上面這道題在不執行`resp.Body.Close()`的情況下,泄漏了嗎?如果泄漏,泄漏了多少個 goroutine? ## 怎么答 不進行 resp.Body.Close(),泄漏是一定的。但是泄漏的 goroutine 個數就讓我迷糊了。由于執行了 6 遍,每次泄漏一個讀和寫 goroutine,就是 12 個 goroutine,加上 main 函數本身也是一個 goroutine,所以答案是 13. 然而執行程序,發現答案是 3,出入有點大,為什么呢? ## 解釋 我們直接看源碼。golang 的 http 包。 ~~~go http.Get() -- DefaultClient.Get ----func (c *Client) do(req *Request) ------func send(ireq *Request, rt RoundTripper, deadline time.Time) -------- resp, didTimeout, err = send(req, c.transport(), deadline) // 以上代碼在 go/1.12.7/libexec/src/net/http/client:174 func (c *Client) transport() RoundTripper { if c.Transport != nil { return c.Transport } return DefaultTransport } ~~~ * 說明`http.Get`默認使用`DefaultTransport`管理連接。 DefaultTransport 是干嘛的呢? ~~~go // It establishes network connections as needed // and caches them for reuse by subsequent calls. ~~~ * `DefaultTransport`的作用是根據需要建立網絡連接并緩存它們以供后續調用重用。 那么`DefaultTransport`什么時候會建立連接呢? 接著上面的代碼堆棧往下翻 ~~~go func send(ireq *Request, rt RoundTripper, deadline time.Time) --resp, err = rt.RoundTrip(req) // 以上代碼在 go/1.12.7/libexec/src/net/http/client:250 func (t *Transport) RoundTrip(req *http.Request) func (t *Transport) roundTrip(req *Request) func (t *Transport) getConn(treq *transportRequest, cm connectMethod) func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (*persistConn, error) { ... go pconn.readLoop() // 啟動一個讀goroutine go pconn.writeLoop() // 啟動一個寫goroutine return pconn, nil } ~~~ * 一次建立連接,就會啟動一個讀 goroutine 和寫 goroutine。這就是為什么一次`http.Get()`會泄漏兩個 goroutine 的來源。 * 泄漏的來源知道了,也知道是因為沒有執行 close **那為什么不執行 close 會泄漏呢?** 回到剛剛啟動的讀 goroutine 的`readLoop()`代碼里 ~~~go func (pc *persistConn) readLoop() { alive := true for alive { ... // Before looping back to the top of this function and peeking on // the bufio.Reader, wait for the caller goroutine to finish // reading the response body. (or for cancelation or death) select { case bodyEOF := <-waitForBodyRead: pc.t.setReqCanceler(rc.req, nil) // before pc might return to idle pool alive = alive && bodyEOF && !pc.sawEOF && pc.wroteRequest() && tryPutIdleConn(trace) if bodyEOF { eofc <- struct{}{} } case <-rc.req.Cancel: alive = false pc.t.CancelRequest(rc.req) case <-rc.req.Context().Done(): alive = false pc.t.cancelRequest(rc.req, rc.req.Context().Err()) case <-pc.closech: alive = false } ... } } ~~~ 其中第一個 body 被讀取完或關閉這個 case: ~~~go alive = alive && bodyEOF && !pc.sawEOF && pc.wroteRequest() && tryPutIdleConn(trace) ~~~ bodyEOF 來源于到一個通道 waitForBodyRead,這個字段的 true 和 false 直接決定了 alive 變量的值(alive=true 那讀 goroutine 繼續活著,循環,否則退出 goroutine)。 **那么這個通道的值是從哪里過來的呢?** ~~~go // go/1.12.7/libexec/src/net/http/transport.go: 1758 body := &bodyEOFSignal{ body: resp.Body, earlyCloseFn: func() error { waitForBodyRead <- false <-eofc // will be closed by deferred call at the end of the function return nil }, fn: func(err error) error { isEOF := err == io.EOF waitForBodyRead <- isEOF if isEOF { <-eofc // see comment above eofc declaration } else if err != nil { if cerr := pc.canceled(); cerr != nil { return cerr } } return err }, } ~~~ * 如果執行 earlyCloseFn ,waitForBodyRead 通道輸入的是 false,alive 也會是 false,那 readLoop() 這個 goroutine 就會退出。 * 如果執行 fn ,其中包括正常情況下 body 讀完數據拋出 io.EOF 時的 case,waitForBodyRead 通道輸入的是 true,那 alive 會是 true,那么 readLoop() 這個 goroutine 就不會退出,同時還順便執行了 tryPutIdleConn(trace) 。 ~~~go // tryPutIdleConn adds pconn to the list of idle persistent connections awaiting // a new request. // If pconn is no longer needed or not in a good state, tryPutIdleConn returns // an error explaining why it wasn't registered. // tryPutIdleConn does not close pconn. Use putOrCloseIdleConn instead for that. func (t *Transport) tryPutIdleConn(pconn *persistConn) error ~~~ * tryPutIdleConn 將 pconn 添加到等待新請求的空閑持久連接列表中,也就是之前說的連接會復用。 那么問題又來了,什么時候會執行這個`fn`和`earlyCloseFn`呢? ~~~go func (es *bodyEOFSignal) Close() error { es.mu.Lock() defer es.mu.Unlock() if es.closed { return nil } es.closed = true if es.earlyCloseFn != nil && es.rerr != io.EOF { return es.earlyCloseFn() // 關閉時執行 earlyCloseFn } err := es.body.Close() return es.condfn(err) } ~~~ * 上面這個其實就是我們比較收悉的 resp.Body.Close() ,在里面會執行 earlyCloseFn,也就是此時 readLoop() 里的 waitForBodyRead 通道輸入的是 false,alive 也會是 false,那 readLoop() 這個 goroutine 就會退出,goroutine 不會泄露。 ~~~go b, err = ioutil.ReadAll(resp.Body) --func ReadAll(r io.Reader) ----func readAll(r io.Reader, capacity int64) ------func (b *Buffer) ReadFrom(r io.Reader) // go/1.12.7/libexec/src/bytes/buffer.go:207 func (b *Buffer) ReadFrom(r io.Reader) (n int64, err error) { for { ... m, e := r.Read(b.buf[i:cap(b.buf)]) // 看這里,是body在執行read方法 ... } } ~~~ * 這個`read`,其實就是`bodyEOFSignal`里的 ~~~go func (es *bodyEOFSignal) Read(p []byte) (n int, err error) { ... n, err = es.body.Read(p) if err != nil { ... // 這里會有一個io.EOF的報錯,意思是讀完了 err = es.condfn(err) } return } func (es *bodyEOFSignal) condfn(err error) error { if es.fn == nil { return err } err = es.fn(err) // 這了執行了 fn es.fn = nil return err } ~~~ * 上面這個其實就是我們比較收悉的讀取 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,這就是為什么代碼明明不規范但卻看不到明顯內存泄漏的原因。 * 那么問題又來了,為什么上面要特意強調是同一個域名呢?改天,回頭,以后有空再說吧。 > 作者:9 號同學 鏈接:[https://juejin.cn/post/6896993332019822605](https://juejin.cn/post/6896993332019822605)來源:掘金 著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看