我遇到了一个go程序占用15gig虚拟内存并继续增长的情况。问题只发生在我们的CentOS服务器上。在我的OSX开发机器上,我无法重现它。
我是否发现了一个错误,或者我做错了什么?
我把问题归结为一个简单的演示,我现在将其描述。首先构建并运行此服务器:
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的文件,并将其chmod为755
#!/bin/bash
sleep 5
然后向/ startapp发出几个并发请求。在bash shell中,您可以这样做:
for i in {1..300}; do (curl http://localhost:8081/startapp &); done
VIRT内存现在应该是几千兆字节。如果重新运行上面的for循环,VIRT内存将每次继续增长千兆字节。
更新1:问题是我在CentOS上遇到了OOM问题。 (谢谢@nos)
更新2 :使用daemonize
并将调用同步到Cmd.Run()
,解决了该问题。感谢@JimB确认在其自己的线程中运行的.Wait()
是POSIX api的一部分,并且没有办法避免在不泄漏资源的情况下调用.Wait()
。
答案 0 :(得分:2)
您发出的每个请求都要求Go在子进程上为Wait
生成一个新的OS线程。每个线程将消耗2MB的堆栈,以及更大的VIRT内存(这不太相关,因为它是虚拟的,但你可能仍然会达到ulimit设置)。 Go运行时重用了线程,但它们目前从未被销毁,因为大多数使用大量线程的程序都会再次这样做。
如果您同时发出300个请求,并在创建任何其他请求之前等待它们完成,则内存应该稳定下来。但是,如果在其他请求完成之前继续发送更多请求,则会耗尽一些系统资源:内存,文件描述符或线程。
关键是产生子进程并调用wait
并不是免费的,如果这是一个真实世界的用例,你需要限制startCmd()
被调用的次数同时。