linux - 执行并发 os/exec.Command.Wait() 时发生内存泄漏

标签 linux go memory-leaks

我遇到了这样一种情况,一个 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/

相关文章:

java - 运行 Kafka 性能流量时出现错误 "This server is not the leader for that topic-partition"

java - 在 Apache Tomcat 中运行的 Web 应用程序中查找内存泄漏的建议

linux - TFS 2015 Linux 构建代理配置抛出 401 错误

c++ - gdb 7.0,SIGCONT 信号不会中断 pause() 调用

go - 为 Kubernetes go-client 使用 HTTP 代理

go - XML 文件的部分索引 (Bleve)

go - 下一个goroutine什么时候执行?

c++ - 可能的内存泄漏?

java - 了解 Java 堆转储

linux - PHP文件在创建虚拟主机后由浏览器下载而不是由apache2处理