You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go语言httpClient协程未关闭引发内存泄漏,求修复方案

Fixing Memory Leaks and Lingering Goroutines in Go http.Client with SOCKS Proxies

Let’s walk through what’s going wrong with your code and fix those memory leaks and unclosed goroutines step by step:

1. Your sync.WaitGroup logic is broken

The second loop in your main code adds unnecessary wg.Add(1) calls and calls wg.Done() on the receiving side—this completely skews the WaitGroup’s counter. The WaitGroup should only track the worker goroutines you spawn to check proxies, not the result-receiving logic. This mismatch is likely why you’re seeing goroutines not terminate properly.

2. Unclosed http.Transport is causing lingering goroutines

Even with DisableKeepAlives: true, the http.Transport spins up background goroutines to manage connections and cleanup. If you don’t explicitly shut down the transport after use, these goroutines will stick around, leading to memory leaks and unclosed goroutines.


Fixed Code

Updated checkProxySOCKS Function

func checkProxySOCKS(prox string, c chan QR, wg *sync.WaitGroup) error {
    defer wg.Done()

    // Handle SOCKS dialer errors properly instead of ignoring them
    dialer, err := proxy.SOCKS5("tcp", prox, nil, proxy.Direct)
    if err != nil {
        c <- QR{Addr: prox, Res: false}
        return err
    }

    timeout := 2 * time.Second
    // Create a dedicated transport for each proxy check
    transport := &http.Transport{
        DisableKeepAlives:     true,
        Dial:                  dialer.Dial,
        ResponseHeaderTimeout: timeout,
        // Add DialContext for better context-aware dialing (matches modern Go practices)
        DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
            return dialer.Dial(network, addr)
        },
    }
    // Critical: Close idle connections to shut down background transport goroutines
    defer transport.CloseIdleConnections()

    httpClient := &http.Client{
        Timeout:   timeout,
        Transport: transport,
    }

    res, err := httpClient.Get("https://telegram.org/")
    if err != nil {
        c <- QR{Addr: prox, Res: false}
        return err
    }
    defer res.Body.Close()

    // Always read the full body to avoid connection leaks (even with DisableKeepAlives)
    _, err = io.Copy(ioutil.Discard, res.Body)
    if err != nil {
        c <- QR{Addr: prox, Res: false}
        return err
    }

    c <- QR{Addr: prox, Res: true}
    return nil
}

Updated Main Goroutine Logic

// Use a buffered channel to prevent worker goroutines from blocking on send
respChan := make(chan QR, len(splitedProxies))
var wg sync.WaitGroup

// Spawn all proxy-checking workers
for _, proxy := range splitedProxies {
    wg.Add(1)
    go checkProxySOCKS(proxy, respChan, &wg)
}

// Wait for all workers to finish, then close the channel to signal the receiver loop
go func() {
    wg.Wait()
    close(respChan)
}()

// Collect all results without messing with WaitGroup
for r := range respChan {
    if r.Res {
        checkedProxiesArray = append(checkedProxiesArray, r.Addr)
    }
}

Key Fixes Explained

  • Correct WaitGroup Usage: We only track the worker goroutines. Once all workers are done, we close the response channel, letting the receiver loop exit gracefully without extra WaitGroup calls.
  • Explicit Transport Cleanup: transport.CloseIdleConnections() ensures that all background goroutines managed by the HTTP transport are shut down, eliminating memory leaks and lingering goroutines.
  • Buffered Channel: Using a buffered channel with the length of your proxy list prevents worker goroutines from blocking if the receiver isn’t ready to read immediately.
  • Better Error Handling: We no longer ignore errors from the SOCKS dialer, and ensure that any failure during body reading still sends a proper result to the channel.

内容的提问来源于stack exchange,提问作者anon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 06:52:38