我遇到了这样一种情况,一个 go 程序占用了 15gig 的虚拟内存并且还在继续增长。这个问题只发生在我们的 CentOS 服务器上。在我的 OSX 开发机器上,我无法重现它。
我是否发现了 go 中的错误,或者我做错了什么?
我已将问题归结为一个简单的演示,现在我将对其进行描述。首先构建并运行这个 go 服务器:
package main
import (
"net/http"
"os/exec"
)
func main() {
http.HandleFunc("/startapp", startAppHandler)
http.ListenAndServe(":8081", nil)
}
func startCmd() {
cmd := exec.Command("/tmp/sleepscript.sh")
cmd.Start()
cmd.Wait()
}
func startAppHandler(w http.ResponseWriter, r *http.Request) {
startCmd()
w.Write([]byte("Done"))
}
创建一个名为/tmp/sleepscript.sh 的文件并将其更改为 755
#!/bin/bash
sleep 5
然后向/startapp 发出多个并发请求。在 bash shell 中,您可以这样做:
for i in {1..300}; do (curl http://localhost:8081/startapp &); done
VIRT 内存现在应该有几千兆字节。如果重新运行上面的 for 循环,VIRT 内存每次都会继续增长 GB。
更新 1: 问题是我在 CentOS 上遇到了 OOM 问题。 (感谢@nos)
更新 2:通过使用 daemonize
并将调用同步到 Cmd.Run()
解决了该问题。感谢@JimB 确认在它自己的线程中运行的 .Wait()
是 POSIX api 的一部分,并且没有办法避免调用 .Wait()
泄漏资源。
最佳答案
您发出的每个请求都需要 Go 生成一个新的操作系统线程以在子进程上 Wait
。每个线程将消耗 2MB 堆栈和更大的 VIRT 内存块(这不太相关,因为它是虚拟的,但您可能仍会遇到 ulimit 设置)。线程被 Go 运行时重用,但它们目前从未被销毁,因为大多数使用大量线程的程序都会再次这样做。
如果您同时发出 300 个请求,并在发出任何其他请求之前等待它们完成,内存应该会稳定下来。但是,如果您在其他请求完成之前继续发送更多请求,您将耗尽一些系统资源:内存、文件描述符或线程。
关键是生成子进程并调用 wait
不是免费的,如果这是一个真实世界的用例,您需要限制 startCmd( )
可以并发调用。
关于linux - 执行并发 os/exec.Command.Wait() 时发生内存泄漏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34346064/