Gat*_*ter 5 linux memory-leaks go
我遇到了这样的情况:一个 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"))
}
Run Code Online (Sandbox Code Playgroud)
创建一个名为 /tmp/sleepscript.sh 的文件并将其 chmod 为 755
#!/bin/bash
sleep 5
Run Code Online (Sandbox Code Playgroud)
然后向 /startapp 发出多个并发请求。在 bash shell 中,您可以这样做:
for i in {1..300}; do (curl http://localhost:8081/startapp &); done
Run Code Online (Sandbox Code Playgroud)
VIRT 内存现在应该有几 GB。如果重新运行上面的 for 循环,VIRT 内存每次都会继续以 GB 为单位增长。
更新 1:问题是我在 CentOS 上遇到 OOM 问题。(感谢@nos)
更新 2:daemonize通过使用调用并将调用同步到 来解决该问题Cmd.Run()。感谢 @JimB 确认.Wait()在它自己的线程中运行是 POSIX api 的一部分,并且没有办法在.Wait()不泄漏资源的情况下避免调用。
Wait您发出的每个请求都需要 Go在子进程上生成一个新的操作系统线程。每个线程将消耗 2MB 堆栈和更大的 VIRT 内存块(这不太相关,因为它是虚拟的,但您可能仍然会遇到 ulimit 设置)。线程被 Go 运行时重用,但目前它们永远不会被销毁,因为大多数使用大量线程的程序都会再次这样做。
如果您同时发出 300 个请求,并等待它们完成后再发出其他请求,那么内存应该会稳定下来。但是,如果您在其他请求完成之前继续发送更多请求,您将耗尽一些系统资源:内存、文件描述符或线程。
关键点是生成子进程和调用不是免费的,如果这是一个现实世界的用例,您需要限制可以并发调用wait的次数。startCmd()