数百个MB内存分配给5个MB的50个请求。内存已分配,不再释放。 如何清除记忆?为什么会发生这种情况?
我已经在家用PC和VPS上尝试过Ubuntu
package main
import (
"fmt"
"io/ioutil"
"net/http"
"time"
)
func main() {
fmt.Println("start")
for i := 0; i < 50; i++ {
go func() {
DoRequest()
}()
time.Sleep(10 * time.Millisecond)
}
time.Sleep(10 * time.Minute)
}
func DoRequest() error {
requestUrl := "https://blockchain.info/rawblock/0000000000000000000eebedea046425bd54626e6c56eb032e66e714d0141ea6"
req, err := http.NewRequest("GET", requestUrl, nil)
if err != nil {
return err
}
req.Header.Set("user-agent", "free")
httpClient := &http.Client{
Timeout: time.Second * 10,
}
resp, err := httpClient.Do(req)
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
return err
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
fmt.Println("bodylen", len(body))
return nil
}
分配了400MB的空间
答案 0 :(得分:7)
您要为每个例程创建一个http客户端。
Http客户端被设计为一次创建并使用多次。他们是例行安全的。 它们允许连接重用和其他节省效率的工具。
在main中(而不是在您的例程中)一次创建http客户端,然后将此唯一引用传递给您的所有50个例程。
编辑:另外,虽然这可能对您没有实际的影响,但请求的顺序通常是这样的:
resp, err := httpClient.Do(req)
if err != nil {
return err // check error first
}
defer resp.Body.Close() // no error - so resp will *NOT* be nil - so this is safe
编辑2:正如@Adrian所说:go
的垃圾回收不是瞬时的-也不应该是瞬时的-因为它是一项昂贵的操作。如果您不再需要内存块,则不再引用它。让GC做好工作,让您可以专心工作!
如果您对go
的GC的发展感到好奇:
答案 1 :(得分:0)
for i := 0; i < 50; i++ {
go func() {
DoRequest()
}()
time.Sleep(10 * time.Millisecond)
}
永远不要创建这样的go例程。始终确保以任何情况下都不能填充大(全部)内存的方式创建例程,
一种简单的解决方案是控制可以在某个时间生成(或运行)的go例程的数量。
您可以通过一次计算要运行的go例程的最大数量来预计算在最坏情况下要占用的内存,并且一个go例程可以使用max-memory。
您可以使用频道控制go-routine的实例。
请参阅此stackoverflow问题的第一个答案 Always have x number of goroutines running at any time
始终在性能和所需资源之间使用平衡的解决方案。
2019年6月11日更新