2015-12-17 27 views
2

我正在運行一種情況,即go程序佔用15gig的虛擬內存並繼續增長。這個問題只發生在我們的CentOS服務器上。在我的OSX開發機上,我無法複製它。執行併發操作時出現內存泄露os/exec.Command.Wait()

我有沒有發現在旅途中的bug,還是我錯誤地做一些事情?

我煮完問題降低到一個簡單的演示,我將現在描述。首先生成並運行此去服務器:

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內存現在應該是幾個GB。如果你重新運行上面的for循環,VIRT內存每次都會繼續增加千兆字節。

更新1:問題是我在CentOS上遇到了OOM問題。 (謝謝@nos)

更新2:通過使用daemonize解決了問題並將調用同步到Cmd.Run()。謝謝@JimB確認運行在它自己的線程中的.Wait()是POSIX API的一部分,並且沒有辦法避免在不泄漏資源的情況下調用.Wait()

+0

VirtualMemory,特別是在OSX上,是沒有意義的。 RSS對於該進程起訴的內存量有一個更好的指導。 – JimB

+0

提示:[Run()](https://golang.org/pkg/os/exec/#Cmd.Run)執行該進程並掛起,直到它退出。 –

+0

我還建議分配一個[goroutines池](https://gobyexample.com/worker-pools)來限制同時運行的進程數量。 –

回答

2

每個請求你讓需要轉到生成一個新的操作系統線程Wait的子進程。每個線程將消耗一個2MB的堆棧,以及一個更大的VIRT內存塊(這是不太相關的,因爲它是虛擬的,但你可能仍然會達到ulimit設置)。 Go運行時會重用線程,但它們目前從未銷燬,因爲大多數使用大量線程的程序會再次執行。

如果你把300個的併發請求,並等待他們作出任何人之前完成,內存應該穩定下來。但是,如果在別人完成之前繼續發送更多請求,則會耗盡一些系統資源:內存,文件描述符或線程。

關鍵是產卵子進程並調用wait不是免費的,如果這是一個真實世界的用例,您需要限制可同時調用startCmd()的次數。

+0

感謝您的回答。這是啓發性的,但我認爲這裏發生了更多的越野車。如果我啓動300條命令並讓它們處於等待狀態,那麼我的進程只使用幾個meram ram。如果我在同一時間啓動這300個命令,那麼我們開始使用10 + gb的VIRT,並且只有在我同時啓動命令時纔會再次增長。 – Gattster

+0

@Gattster這是「完全相同的時間」的部分,這是問題。如果「wait」被連續調用,線程可以被重用,但是當所有現有線程都處於阻塞系統調用時,運行時需要產生一個新線程繼續運行。查看兩個示例之間的流程中的線程數。 – JimB

+0

謝謝@JimB。我正在考慮用'daemonize/tmp/sleepscript.sh'運行長時間運行的子進程並將我的調用同步到'Cmd.Run()'來避免這個問題。 – Gattster