Goroutine-Verwaltung
Goroutinen sind zwar leichtgewichtig, aber nicht kostenlos: Sie verbrauchen zumindest Speicherplatz für den Stapel und die CPU-Planung. Obwohl diese Kosten für die Verwendung von Goroutinen gering sind, können große Mengen von ihnen ohne kontrollierten Lebenszyklus zu ernsthaften Leistungsproblemen führen. Goroutinen mit unverwalteten Lebenszyklen können auch zu anderen Problemen führen, wie z. B. dem Verhindern der Bereinigung von ungenutzten Objekten und dem Beibehalten ungenutzter Ressourcen.
Daher sollten in Ihrem Code keine Goroutinen undicht sein. Verwenden Sie go.uber.org/goleak, um auf Goroutinenlecks innerhalb eines Pakets zu testen, die Goroutinen erzeugen können.
Im Allgemeinen muss jede Goroutine entweder:
- eine vorhersagbare Beendigungszeit haben; oder
- eine Möglichkeit haben, der Goroutine zu signalisieren, dass sie aufhören soll.
In beiden Fällen muss es einen Weg geben, damit der Code blockiert und auf die Beendigung der Goroutine wartet.
Zum Beispiel:
Nicht empfohlen:
go func() {
for {
flush()
time.Sleep(delay)
}
}()
// Es gibt keine Möglichkeit, diese Goroutine zu stoppen. Sie wird unendlich laufen, bis die Anwendung beendet wird.
Empfohlen:
var (
stop = make(chan struct{}) // Signal to stop the goroutine
done = make(chan struct{}) // Signal that the goroutine has exited
)
go func() {
defer close(done)
ticker := time.NewTicker(delay)
defer ticker.Stop()
for {
select {
case <-ticker.C:
flush()
case <-stop:
return
}
}
}()
// ... anderer Code
close(stop) // Anzeigen, dass die Goroutine gestoppt werden soll
<-done // und auf ihr Ende warten
// Diese Goroutine kann mit close(stop) gewartet werden, und wir können auf ihr Ende warten <-done.
Warten auf das Beenden von Goroutinen
Wenn eine vom System generierte Goroutine vorliegt, muss es eine Möglichkeit geben, auf das Beenden der Goroutine zu warten. Es gibt zwei gängige Methoden, um dies zu erreichen:
- Verwendung von
sync.WaitGroup
. Verwenden Sie diese Methode, wenn auf mehrere Goroutinen gewartet wird.
var wg sync.WaitGroup
for i := 0; i < num; i++ {
wg.Add(1)
go func() {
defer wg.Done()
// ...
}()
}
wg.Wait()
- Hinzufügen eines weiteren
chan struct{}
und Schließen nachdem die Goroutine abgeschlossen ist. Verwenden Sie diese Methode, wenn es nur eine Goroutine gibt.
done := make(chan struct{})
go func() {
defer close(done)
// ...
}()
// Um auf das Beenden der Goroutine zu warten:
<-done
Die init()
-Funktion sollte keine Goroutinen erstellen. Beachten Sie auch Vermeiden Sie die Verwendung von init().
Wenn ein Paket eine Hintergrund-Goroutine erfordert, muss es ein Objekt freigeben, das für die Verwaltung des Lebenszyklus der Goroutine verantwortlich ist. Dieses Objekt muss eine Methode (Close
, Stop
, Shutdown
usw.) bereitstellen, um die Beendigung der Hintergrund-Goroutine anzuzeigen und auf ihr Ende zu warten.
Nicht empfohlener Ansatz:
func init() {
go doWork()
}
func doWork() {
for {
// ...
}
}
// Wenn der Benutzer dieses Paket exportiert, wird eine Hintergrund-Goroutine bedingungslos erzeugt. Benutzer können die Goroutine nicht steuern oder stoppen.
Empfohlener Ansatz:
type Worker struct{ /* ... */ }
func NewWorker(...) *Worker {
w := &Worker{
stop: make(chan struct{}),
done: make(chan struct{}),
// ...
}
go w.doWork()
}
func (w *Worker) doWork() {
defer close(w.done)
for {
// ...
select {
case <-w.stop:
return
}
}
}
// Shutdown teilt dem Worker mit, aufzuhören
// und wartet darauf, dass er fertig ist.
func (w *Worker) Shutdown() {
close(w.stop)
<-w.done
}
Generieren Sie den Worker nur bei Bedarf durch den Benutzer und stellen Sie eine Methode zum Herunterfahren des Workers zur Verfügung, damit der Benutzer die Ressourcen, die vom Worker verwendet werden, freigeben kann.
Beachten Sie, dass, wenn der Worker mehrere Goroutinen verwaltet, eine WaitGroup verwendet werden sollte.