Wie man eine Goroutine stoppt

102

Ich habe eine Goroutine, die eine Methode aufruft und den zurückgegebenen Wert auf einem Kanal übergibt:

ch := make(chan int, 100)
go func(){
    for {
        ch <- do_stuff()
    }
}()

Wie stoppe ich eine solche Goroutine?

Łukasz Gruner
quelle
1
Eine andere Antwort, abhängig von Ihrer Situation, ist die Verwendung eines Go-Kontexts. Ich habe weder die Zeit noch das Wissen, um eine Antwort darauf zu erstellen. Ich wollte es hier nur erwähnen, damit Leute, die diese Antwort suchen und unbefriedigend finden, einen anderen Thread ziehen können (Wortspiel beabsichtigt). In den meisten Fällen sollten Sie tun, was die akzeptierte Antwort nahelegt. Diese Antwort erwähnt Kontexte: stackoverflow.com/a/47302930/167958
Omnifarious

Antworten:

50

EDIT: Ich habe diese Antwort in Eile geschrieben, bevor mir klar wurde, dass es bei Ihrer Frage darum geht, Werte an einen Chan innerhalb einer Goroutine zu senden. Der folgende Ansatz kann entweder mit einem zusätzlichen Chan verwendet werden, wie oben vorgeschlagen, oder unter Verwendung der Tatsache, dass der Chan, den Sie bereits haben, bidirektional ist, können Sie nur den einen ...

Wenn Ihre Goroutine nur zur Verarbeitung der aus dem Chan kommenden Elemente vorhanden ist, können Sie das integrierte "Schließen" und das spezielle Empfangsformular für Kanäle verwenden.

Das heißt, sobald Sie mit dem Senden von Elementen auf dem Chan fertig sind, schließen Sie ihn. Dann erhalten Sie in Ihrer Goroutine einen zusätzlichen Parameter für den Empfangsoperator, der anzeigt, ob der Kanal geschlossen wurde.

Hier ist ein vollständiges Beispiel (die Wartegruppe wird verwendet, um sicherzustellen, dass der Prozess fortgesetzt wird, bis die Goroutine abgeschlossen ist):

package main

import "sync"
func main() {
    var wg sync.WaitGroup
    wg.Add(1)

    ch := make(chan int)
    go func() {
        for {
            foo, ok := <- ch
            if !ok {
                println("done")
                wg.Done()
                return
            }
            println(foo)
        }
    }()
    ch <- 1
    ch <- 2
    ch <- 3
    close(ch)

    wg.Wait()
}
laslowh
quelle
15
Der Körper der inneren Goroutine ist idiomatischer geschrieben, indem deferaufgerufen wird wg.Done(), und eine range chSchleife, um alle Werte zu durchlaufen, bis der Kanal geschlossen wird.
Alan Donovan
115

In der Regel übergeben Sie der Goroutine einen (möglicherweise separaten) Signalkanal. Dieser Signalkanal wird verwendet, um einen Wert zu drücken, wenn die Goroutine gestoppt werden soll. Die Goroutine fragt diesen Kanal regelmäßig ab. Sobald es ein Signal erkennt, wird es beendet.

quit := make(chan bool)
go func() {
    for {
        select {
        case <- quit:
            return
        default:
            // Do other stuff
        }
    }
}()

// Do stuff

// Quit goroutine
quit <- true
jimt
quelle
26
Nicht gut genug. Was ist, wenn die Goroutine aufgrund eines Fehlers in einer Endlosschleife steckt?
Elazar Leibovich
232
Dann sollte der Fehler behoben sein.
Jimt
13
Elazar, Sie schlagen vor, eine Funktion zu stoppen, nachdem Sie sie aufgerufen haben. Eine Goroutine ist kein Faden. Es kann in einem anderen Thread oder in demselben Thread wie Ihres ausgeführt werden. Ich kenne keine Sprache, die das unterstützt, was Go Ihrer Meinung nach unterstützen sollte.
Jeremy Wall
5
@jeremy Sie sind nicht anderer Meinung als Go, aber mit Erlang können Sie einen Prozess beenden, auf dem eine Schleifenfunktion ausgeführt wird.
MatthewToday
10
Go Multitasking ist kooperativ und nicht präventiv. Eine Goroutine in einer Schleife tritt niemals in den Scheduler ein, sodass sie niemals getötet werden kann.
Jeff Allen
34

Sie können eine Goroutine nicht von außen töten. Sie können einer Goroutine signalisieren, dass sie keinen Kanal mehr verwenden soll, aber Goroutinen haben keine Möglichkeit, Meta-Management zu betreiben. Goroutinen sollen Probleme kooperativ lösen, daher wäre es fast nie angemessen, jemanden zu töten, der sich schlecht benimmt. Wenn Sie Isolation für Robustheit wünschen, möchten Sie wahrscheinlich einen Prozess.

SteveMcQwark
quelle
Vielleicht möchten Sie sich auch das Paket encoding / gob ansehen, mit dem zwei Go-Programme problemlos Datenstrukturen über eine Pipe austauschen können.
Jeff Allen
In meinem Fall habe ich eine Goroutine, die bei einem Systemaufruf blockiert wird, und ich muss sie anweisen, den Systemaufruf abzubrechen und dann zu beenden. Wenn ich auf einem Kanal gelesen würde, wäre es möglich, das zu tun, was Sie vorschlagen.
Omnifarious
Ich habe dieses Problem schon einmal gesehen. Die Art und Weise, wie wir es "gelöst" haben, bestand darin, die Anzahl der Threads zu Beginn der Anwendung zu erhöhen, um sie an die Anzahl der Goroutinen anzupassen, die möglicherweise + die Anzahl der CPUs erreichen
könnten
18

Im Allgemeinen können Sie einen Kanal erstellen und ein Stoppsignal in der Goroutine empfangen.

In diesem Beispiel gibt es zwei Möglichkeiten, einen Kanal zu erstellen.

  1. Kanal

  2. Kontext . Im Beispiel werde ich Democontext.WithCancel

Die erste Demo, verwenden Sie channel:

package main

import "fmt"
import "time"

func do_stuff() int {
    return 1
}

func main() {

    ch := make(chan int, 100)
    done := make(chan struct{})
    go func() {
        for {
            select {
            case ch <- do_stuff():
            case <-done:
                close(ch)
                return
            }
            time.Sleep(100 * time.Millisecond)
        }
    }()

    go func() {
        time.Sleep(3 * time.Second)
        done <- struct{}{}
    }()

    for i := range ch {
        fmt.Println("receive value: ", i)
    }

    fmt.Println("finish")
}

Die zweite Demo verwenden context:

package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
    forever := make(chan struct{})
    ctx, cancel := context.WithCancel(context.Background())

    go func(ctx context.Context) {
        for {
            select {
            case <-ctx.Done():  // if cancel() execute
                forever <- struct{}{}
                return
            default:
                fmt.Println("for loop")
            }

            time.Sleep(500 * time.Millisecond)
        }
    }(ctx)

    go func() {
        time.Sleep(3 * time.Second)
        cancel()
    }()

    <-forever
    fmt.Println("finish")
}
zouying
quelle
11

Ich weiß , diese Antwort wird bereits angenommen, aber ich dachte , dass ich meine 2cents in werfen würde. Ich mag das verwenden , Grab - Paket. Es ist im Grunde ein beschleunigter Beendigungskanal, aber es macht auch nette Dinge wie das Zurückgeben von Fehlern. Die kontrollierte Routine hat weiterhin die Verantwortung, nach Remote-Kill-Signalen zu suchen. Afaik ist es nicht möglich, eine "ID" einer Goroutine zu erhalten und sie zu töten, wenn sie sich schlecht benimmt (dh in einer Endlosschleife steckt).

Hier ist ein einfaches Beispiel, das ich getestet habe:

package main

import (
  "launchpad.net/tomb"
  "time"
  "fmt"
)

type Proc struct {
  Tomb tomb.Tomb
}

func (proc *Proc) Exec() {
  defer proc.Tomb.Done() // Must call only once
  for {
    select {
    case <-proc.Tomb.Dying():
      return
    default:
      time.Sleep(300 * time.Millisecond)
      fmt.Println("Loop the loop")
    }
  }
}

func main() {
  proc := &Proc{}
  go proc.Exec()
  time.Sleep(1 * time.Second)
  proc.Tomb.Kill(fmt.Errorf("Death from above"))
  err := proc.Tomb.Wait() // Will return the error that killed the proc
  fmt.Println(err)
}

Die Ausgabe sollte folgendermaßen aussehen:

# Loop the loop
# Loop the loop
# Loop the loop
# Loop the loop
# Death from above
Kevin Cantwell
quelle
Dieses Paket ist sehr interessant! Haben Sie getestet, was tombmit der Goroutine passiert, falls etwas in ihr passiert, das zum Beispiel Panik auslöst? Technisch gesehen wird die Goroutine in diesem Fall beendet, daher gehe ich davon aus, dass die verzögerte proc.Tomb.Done()...
Gwyneth Llewelyn
1
Hallo Gwyneth, proc.Tomb.Done()würde ja ausführen, bevor die Panik das Programm zum Absturz bringt, aber zu welchem ​​Zweck? Es ist möglich, dass die Hauptgoroutine ein sehr kleines Zeitfenster hat, um einige Anweisungen auszuführen, aber es gibt keine Möglichkeit, sich von einer Panik in einer anderen Goroutine zu erholen, sodass das Programm immer noch abstürzt. Die Ärzte sagen: "Wenn die Funktion F Panik auslöst, wird die Ausführung von F gestoppt, alle zurückgestellten Funktionen in F werden normal ausgeführt, und dann kehrt F zu seinem Aufrufer zurück. Der Prozess setzt den Stapel fort, bis alle Funktionen in der aktuellen Goroutine zurückgekehrt sind. An diesem Punkt stürzt das Programm ab. "
Kevin Cantwell