Ich habe ein Go-Programm, das kontinuierlich läuft und vollständig auf goroutines + 1 manager
Thread basiert. Der Haupt-Thread ruft einfach Goroutines an und schläft ansonsten.Golang Memory Leak in Bezug auf Goroutines
Es liegt ein Speicherleck vor. Das Programm verwendet mehr und mehr Speicher, bis es alle 16 GB RAM + 32 GB SWAP ableitet und dann jede goroutine in Panik gerät. Es ist tatsächlich OS-Speicher, der die Panik verursacht, in der Regel ist die Panik fork/exec ./anotherapp: cannot allocate memory
, wenn ich versuche, anotherapp
auszuführen.
Wenn dies passiert, werden alle Worker-Threads in Panik versetzt und wiederhergestellt und neu gestartet. So wird jede goroutine in Panik geraten, wiederhergestellt und neu gestartet ... an diesem Punkt wird die Speichernutzung nicht abnehmen, sie bleibt bei 48GB, obwohl jetzt praktisch nichts zugewiesen ist. Das bedeutet, dass alle Goroutinen immer in Panik geraten, da nie genug Speicher zur Verfügung steht, bis die gesamte ausführbare Datei beendet und vollständig neu gestartet wurde.
Das Ganze ist etwa 50.000 Zeilen, aber der eigentliche Problembereich ist wie folgt:
type queue struct {
identifier string
type bool
}
func main() {
// Set number of gorountines that can be run
var xthreads int32 = 10
var usedthreads int32
runtime.GOMAXPROCS(14)
ready := make(chan *queue, 5)
// Start the manager goroutine, which prepared identifiers in the background ready for processing, always with 5 waiting to go
go manager(ready)
// Start creating goroutines to process as they are ready
for obj := range ready { // loops through "ready" channel and waits when there is nothing
// This section uses atomic instead of a blocking channel in an earlier attempt to stop the memory leak, but it didn't work
for atomic.LoadInt32(&usedthreads) >= xthreads {
time.Sleep(time.Second)
}
debug.FreeOSMemory() // Try to clean up the memory, also did not stop the leak
atomic.AddInt32(&usedthreads, 1) // Mark goroutine as started
// Unleak obj, probably unnecessary, but just to be safe
copy := new(queue)
copy.identifier = unleak.String(obj.identifier) // unleak is a 3rd party package that makes a copy of the string
copy.type = obj.type
go runit(copy, &usedthreads) // Start the processing thread
}
fmt.Println(`END`) // This should never happen as the channels are never closed
}
func manager(ready chan *queue) {
// This thread communicates with another server and fills the "ready" channel
}
// This is the goroutine
func runit(obj *queue, threadcount *int32) {
defer func() {
if r := recover(); r != nil {
// Panicked
erstring := fmt.Sprint(r)
reportFatal(obj.identifier, erstring)
} else {
// Completed successfully
reportDone(obj.identifier)
}
atomic.AddInt32(threadcount, -1) // Mark goroutine as finished
}()
do(obj) // This function does the actual processing
}
Soweit ich sehen kann, wenn die do
Funktion (letzte Zeile) endet, entweder durch mit fertiger oder In Panik geraten endet dann die runit
Funktion, die die Goroutine vollständig beendet, was bedeutet, dass der gesamte Speicher von dieser Goroutine nun frei sein sollte. Das ist jetzt was passiert. Was passiert ist, dass diese App nur mehr und mehr und mehr Speicher verwendet, bis es nicht mehr funktionsfähig ist, und die Speicher nicht abnimmt.
Profiling zeigt nichts Verdächtiges. Das Leck scheint außerhalb des Bereichs des Profilers zu liegen.
Ich würde den Code versuchen Sie _without_ den Code geschrieben Sie nicht gebucht haben. Verwenden Sie eine 'manager()' -Funktion, die nur endlos eine Eingabe generiert, und eine 'do()' -Funktion, die nichts tut (leere Funktion). Sehen Sie nach, ob Sie immer noch ein Speicherleck haben. Wenn nicht, dann ist das Leck offensichtlich in dem Code, den Sie nicht gepostet haben. In diesem Fall können wir im aktuellen Zustand der Frage nichts tun. – icza
Verwenden Sie unsafe oder C überall? Neueste Go-Version? Ich würde versuchen, es mit GODEBUG = gctrace = 1 auszuführen, um zu überprüfen, was mit dem Garbage Collector vor sich geht. – siritinga
Ich weiß nicht, ob es beabsichtigt ist oder nicht, aber es gibt keine Garantie, dass dieser Code maximal 10 Goroutines verwendet. Wenn Sie die Anzahl der Worker auf 10 begrenzen möchten, tun Sie dies [http://play.golang.org/p/ADc99JTMBJ]. Der obige Code hat einen Feldnamen 'type', der nicht kompiliert wird. Können Sie den tatsächlichen Code anzeigen? –