为了账号安全,请及时绑定邮箱和手机立即绑定

golang中的多部分表单上传+内存泄漏?

golang中的多部分表单上传+内存泄漏?

Go
小唯快跑啊 2021-10-11 13:41:55
以下服务器代码:package mainimport (  "fmt"  "net/http")func handler(w http.ResponseWriter, r *http.Request) {  file, _, err := r.FormFile("file")  if err != nil {    fmt.Fprintln(w, err)    return  }  defer file.Close()  return}func main() {  http.ListenAndServe(":8081", http.HandlerFunc(handler))}正在运行,然后使用以下命令调用它:curl -i -F "file=@./large-file" --form hello=world http://localhost:8081/在large-filedarwin/amd64 和 linux/amd64 上的 Go 1.4.2 中,大约 80MB 似乎存在某种形式的内存泄漏。当我连接时pprof,我看到bytes.makeSlice在多次调用服务后使用了 96MB 的内存(最终r.FormFile在我上面的代码中调用)。如果我继续调用curl,进程的内存使用会随着时间的推移而变慢,最终似乎在我的机器上停留在 300MB 左右。想法?我认为这不是预期的/我做错了什么?
查看完整描述

1 回答

?
一只名叫tom的猫

TA贡献1906条经验 获得超3个赞

如果内存使用量停滞在“最大值”,我不会真正称其为内存泄漏。我宁愿说 GC 不急切和懒惰。或者只是不想物理释放内存,如果它经常被重新分配/需要。如果真的是内存泄漏,使用的内存不会停留在 300 MB。


r.FormFile("file")将导致调用Request.ParseMultipartForm(),并且 32 MB 将用作maxMemory参数的值(在 中defaultMaxMemory定义的变量的值request.go)。由于您上传了一个更大的文件 (80 MB),因此至少会创建一个大小为 32 MB 的缓冲区 - 最终(这在 中实现multipart.Reader.ReadFrom())。由于bytes.Buffer用于读取内容,读取过程将从一个小的或空的缓冲区开始,并在需要更大的缓冲区时重新分配。


缓冲区重新分配的策略和缓冲区大小取决于实现(并且还取决于从请求中读取/解码的块的大小),但只是为了有一个粗略的图片,想象一下:0 字节,4 KB, 16 KB、64 KB、256 KB、1 MB、4 MB、16 MB、64 MB。同样,这只是理论上的,但说明总和甚至可以增长超过 100 MB,只是为了读取内存中文件的前 32 MB,此时将决定将其移动/存储在文件中。有关multipart.Reader.ReadFrom()详细信息,请参阅 的实现。这合理地解释了 96 MB 的分配。


这样做几次,如果 GC 不立即释放分配的缓冲区,您很容易得到 300 MB。并且如果有足够的空闲内存,GC 就没有压力急于释放内存。你看到它增长相对较大的原因是因为在后台使用了大缓冲区。您是否会在上传 1MB 文件时做同样的事情,您可能不会遇到这种情况。


如果它对您很重要,您也可以Request.ParseMultipartForm()手动调用较小的maxMemory值,例如


r.ParseMultipartForm(2 << 20) // 2 MB

file, _, err := r.FormFile("file")

// ... rest of your handler

在后台分配更小(和更少)的缓冲区。


查看完整回答
反对 回复 2021-10-11
  • 1 回答
  • 0 关注
  • 341 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信