Heim  >  Artikel  >  Backend-Entwicklung  >  Das Go-Programm wird beendet, bevor die Goroutine-Arbeit abgeschlossen ist

Das Go-Programm wird beendet, bevor die Goroutine-Arbeit abgeschlossen ist

PHPz
PHPznach vorne
2024-02-08 22:57:20841Durchsuche

Go 程序在 goroutine 工作完成之前退出

In diesem Artikel stellt der PHP-Editor Xiaoxin ein wichtiges Thema zu Go-Programmen vor: die Situation des Beendens, bevor die Goroutine-Arbeit abgeschlossen ist. In der Go-Sprache ist Goroutine ein leichter Thread, der Aufgaben gleichzeitig ausführen kann. Wenn unser Programm jedoch möglicherweise beendet wird, bevor die Goroutine-Arbeit abgeschlossen ist, müssen wir verstehen, wie wir mit dieser Situation umgehen, um sicherzustellen, dass unser Programm die Aufgabe korrekt ausführen kann. Im folgenden Inhalt werden wir dieses Problem untersuchen und einige Lösungen zu seiner Lösung bereitstellen.

Frageninhalt

Ich habe Schwierigkeiten zu verstehen, wie man Kanäle richtig blockiert und schließt. Ich starte eine beliebige Anzahl von Workern und stelle fest, dass meine Hauptfunktion entweder beendet wird, bevor die Worker fertig sind, oder aufgrund nicht geschlossener Kanäle hängt. Ich brauche eine bessere Möglichkeit, den Worker daran zu hindern, den Kanal zu lesen, ohne den Hauptkanal zu verlassen, und den Kanal dann ordnungsgemäß zu schließen, wenn er fertig ist, um die Schleife zu beenden. Alle Versuche, die ich unternehme, enden im Stillstand.

Ich habe ein paar Dinge versucht, unter anderem die Verwendung einer Wartegruppe, aber das Problem besteht weiterhin. Mir ist aufgefallen, dass das Programm durch das Hinzufügen von time.sleep wie erwartet funktioniert, das Auskommentieren jedoch dazu führt, dass keine Arbeit erledigt wird.

time.sleep(time.duration(10 * time.second))

Hier ist ein funktionierendes Beispiel https://go.dev/play/p/qhqnj-ajqbi mit sleep erhalten. Dies ist der fehlerhafte Code mit auskommentiertem Ruhezeit-Timeout.

package main

import (
    "fmt"
    "sync"
    "time"
)

// some complicated work
func do(num int, ch chan<- int) {
    time.sleep(time.duration(500 * time.millisecond))
    ch <- num
}

func main() {

    results := make(chan int)

    // for some number of required complicated work
    for i := 0; i < 53; i++ {
        go do(i, results)
    }

    var wg sync.waitgroup

    // start 3 workers which can process results
    for i := 0; i < 3; i++ {
        wg.add(1)
        go func(id int) {
            defer wg.done()
            worker(id, results)
        }(i)
    }

    // handle closing the channel when all workers complete
    go func() {
        wg.wait()
        close(results)
    }()

    //time.sleep(time.duration(10 * time.second))

    fmt.println("donezo")
}

// process the results of do() in a meaningful way
func worker(id int, ch <-chan int) {
    fmt.println("starting worker", id)

    for i := range ch {
        fmt.println("channel val:", i)
    }
}

Ich habe auch versucht, defer wg.done() 移动到 worker() in func einzufügen, aber es ist das gleiche Problem und funktioniert nicht ohne Schlaf.

// process the results of do() in a meaningful way
func worker(wg *sync.WaitGroup, id int, ch <-chan int) {
    fmt.Println("starting worker", id)

    defer wg.Done()

    for i := range ch {
        fmt.Println("channel val:", i)
    }
}

Habe ich das falsche Paradigma gewählt oder verwende ich einfach das falsche Paradigma?

Workaround

Ich habe ursprünglich gefragt: „Kann ich ein paar kleine Anpassungen an meinem Code vornehmen, damit er funktioniert? Oder muss ich das noch einmal überdenken? “ Die Antwort, die ich gefunden habe, ist: Ja, es gibt eine kleine Anpassung.

Ich musste ein interessantes Grundkonzept über Kanäle lernen: Man kann Daten aus einem geschlossenen Kanal lesen, also den Kanal entleeren. Wie in meinem ursprünglichen Beispiel erwähnt range wird es nie beendet, weil ich keinen guten Ort zum Schließen des Kanals finde, und selbst wenn ich es auf andere kreative Weise erzwinge, zeigt das Programm schlechtes Verhalten

  • Beendet, ohne dass alle Inhalte im Kanal verarbeitet wurden
  • Deadlock oder Senden auf geschlossenem Kanal

Dies ist auf einen subtilen Unterschied im „echten“-Code zurückzuführen, bei dem die Zeit, die zum Verarbeiten des Kanalinhalts benötigt wird, länger ist als die Zeit, die zum Befüllen des Kanals benötigt wird, und die Dinge nicht synchron sind.

Da es in meinem Sender keine klare praktische Möglichkeit gibt, den Kanal zu schließen (was in 99 % der Kanal-Tutorials empfohlen wird), wenn mehrere Mitarbeiter den Kanal lesen und die Mitarbeiter nichts davon wissen, ist dies per Goroutine in der Tat der Fall akzeptabel, dies in main zu tun, wo der letzte Wert gelesen wird.

Lösung

Ich habe den Worker in seine eigene sync.waitgroup eingeschlossen und worker.wait() verwendet, um sync.waitgroup 中,并使用 worker.wait()阻止程序退出,从而允许工作“完成” ”。当没有更多数据要发送时,我独立地 close()

zu verhindern, dass

das Programm beendet wird, sodass die Arbeit „abgeschlossen“ werden kann. ". Wenn keine weiteren Daten mehr zu senden sind, close() ich die Kanäle unabhängig voneinander, d. h. ich blockiere, indem ich darauf warte, dass der Writer mit seiner eigenen Wartegruppe fertig ist. close bietet eine Beendigung für den Fall von Bereichsschleifen , denn wenn der Standardwert des Kanals zurückgegeben wird, d. h. der EOF-Typ wird zurückgegeben, wenn das Ende des Kanals erreicht ist, wird der Schnittkanal blockiert, bis er geschlossen wird . workers.wait()Meine Meinung dazu ist: Wenn Sie nicht wissen, wie viele Werte parallel übertragen werden, hat go keine Möglichkeit, die Länge des ungepufferten Kanals zu kennen, da dieser im Gültigkeitsbereich liegt,

bis Sie ihn schließen.

. Da es geschlossen ist, bedeutet es, alles zu lesen, was bis zum Endwert oder Ende übrig bleibt. wird bis zum Abschluss blockiert.

Beispiele für gelöste Vorgänge

https://www.php.cn/link/2bf0ccdbb4d3ebbcb990af74bd78c658

Beispiel für das Lesen eines geschlossenen Kanals

https://www.php.cn/link/d5397f1497b5cdaad7253fdc92db610b

🎜 🎜Ausgabe🎜
filling 0
filling 1
filling 2
filling 3
filling 4
filling 5
filling 6
filling 7
filling 8
filling 9
closed
empyting 0
empyting 1
empyting 2
empyting 3
empyting 4
empyting 5
empyting 6
empyting 7
empyting 8
empyting 9

Das obige ist der detaillierte Inhalt vonDas Go-Programm wird beendet, bevor die Goroutine-Arbeit abgeschlossen ist. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:stackoverflow.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen