执行并发操作时执行内存泄漏操作/ exec.Command.Wait() [英] Go memory leak when doing concurrent os/exec.Command.Wait()

查看:339
本文介绍了执行并发操作时执行内存泄漏操作/ exec.Command.Wait()的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我遇到了一个去程序占用15gig虚拟内存并继续增长的情况。这个问题只发生在我们的CentOS服务器上。在我的OSX开发机上,我无法复制它。



我是否发现了一个错误,或者我做错了什么?

下面我将介绍一个简单的演示。首先建立并运行这个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&);完成

VIRT内存现在应该是几千兆字节。如果你重新运行上面的for循环,VIRT内存每次都会继续增加千兆字节。

更新1:问题是我在CentOS上遇到了OOM问题。 (感谢@nos)

更新2:通过使用 daemonize 并将调用同步到 Cmd.Run()。感谢@JimB确认在它自己的线程中运行的 .Wait()是POSIX api的一部分,并且没有办法避免调用 .Wait()不会泄露资源。

解决方案

每个请求都需要Go生成一个新的OS线程到 Wait 关于子进程。每个线程将消耗一个2MB的堆栈,以及一个更大的VIRT内存块(这是不太相关的,因为它是虚拟的,但你可能仍然会达到ulimit设置)。线程由Go运行时重用,但它们目前从未销毁,因为大多数使用大量线程的程序都会再次执行。



如果您同时创建300个线程请求,并等待他们完成之前,使任何其他人,内存应稳定。但是,如果在别人完成之前继续发送更多请求,则会耗尽一些系统资源:内存,文件描述符或线程。



关键在于产生一个子进程并调用 wait 不是免费的,如果这是一个您需要限制次数 startCmd()的实际用例可以同时调用。

I am running into a situation where a go program is taking up 15gig of virtual memory and continues to grow. The problem only happens on our CentOS server. On my OSX devel machine, I can't reproduce it.

Have I discovered a bug in go, or am I doing something incorrectly?

I have boiled the problem down to a simple demo, which I'll describe now. First build and run this go server:

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"))
}

Make a file named /tmp/sleepscript.sh and chmod it to 755

#!/bin/bash
sleep 5

And then make several concurrent requests to /startapp. In a bash shell, you can do it this way:

for i in {1..300}; do (curl http://localhost:8081/startapp &); done

The VIRT memory should now be several gigabytes. If you re-run the above for loop, the VIRT memory will continue to grow by gigabytes every time.

Update 1: The problem is that I am hitting OOM issues on CentOS. (thanks @nos)

Update 2: Worked around the problem by using daemonize and syncing the calls to Cmd.Run(). Thanks @JimB for confirming that .Wait() running in it's own thread is part of the POSIX api and there isn't a way to avoid calling .Wait() without leaking resources.

解决方案

Each request you make requires Go to spawn a new OS thread to Wait on the child process. Each thread will consume a 2MB stack, and a much larger chunk of VIRT memory (that's less relevant, since it's virtual, but you may still be hitting a ulimit setting). Threads are reused by the Go runtime, but they are currently never destroyed, since most programs that use a large number of threads will do so again.

If you make 300 simultaneous requests, and wait for them to complete before making any others, memory should stabilize. However if you continue to send more requests before the others have completed, you will exhaust some system resource: either memory, file descriptors, or threads.

The key point is that spawning a child process and calling wait isn't free, and if this were a real-world use case you need to limit the number of times startCmd() can be called concurrently.

这篇关于执行并发操作时执行内存泄漏操作/ exec.Command.Wait()的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆