Come fermare una goroutine


102

Ho una goroutine che chiama un metodo e passa il valore restituito su un canale:

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

Come faccio a fermare una simile goroutine?


1
Un'altra risposta, a seconda della tua situazione, è usare un contesto Go. Non ho il tempo o le conoscenze per creare una risposta al riguardo. Volevo solo menzionarlo qui in modo che le persone che cercano e trovano questa risposta insoddisfacente abbiano un altro filo da tirare (gioco di parole). Nella maggior parte dei casi, dovresti fare come suggerisce la risposta accettata. Questa risposta cita contesti: stackoverflow.com/a/47302930/167958
omnifarious

Risposte:


51

EDIT: Ho scritto questa risposta in fretta, prima di rendermi conto che la tua domanda riguarda l'invio di valori a un canale all'interno di una goroutine. L'approccio di seguito può essere utilizzato con un canale aggiuntivo come suggerito sopra, o utilizzando il fatto che il canale che hai già è bidirezionale, puoi usare solo quello ...

Se la tua goroutine esiste esclusivamente per elaborare gli elementi che escono dal canale, puoi utilizzare il builtin "chiudi" e il modulo di ricezione speciale per i canali.

Cioè, una volta che hai finito di inviare elementi sul canale, lo chiudi. Quindi all'interno della tua goroutine ottieni un parametro extra per l'operatore di ricezione che mostra se il canale è stato chiuso.

Ecco un esempio completo (il gruppo di attesa viene utilizzato per assicurarsi che il processo continui fino al completamento della goroutine):

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()
}

16
Il corpo della goroutine interna è scritto più idiomaticamente usando deferto call wg.Done()e un range chciclo per iterare su tutti i valori fino a quando il canale non viene chiuso.
Alan Donovan

115

In genere, si passa alla goroutine un canale di segnale (possibilmente separato). Quel canale del segnale viene utilizzato per inserire un valore quando si desidera che la goroutine si fermi. La goroutine interroga regolarmente quel canale. Non appena rileva un segnale, si chiude.

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

// Do stuff

// Quit goroutine
quit <- true

26
Non buono abbastanza. E se la goroutine fosse bloccata in un ciclo infinito, a causa di un bug?
Elazar Leibovich

232
Quindi il bug dovrebbe essere corretto.
jimt

13
Elazar, quello che suggerisci è un modo per interrompere una funzione dopo averla chiamata. Una goroutine non è un thread. Può essere eseguito in un thread diverso o può essere eseguito nello stesso thread del tuo. Non conosco alcuna lingua che supporti ciò che pensi che Go dovrebbe supportare.
Jeremy Wall

5
@jeremy Non sono in disaccordo per Go, ma Erlang ti consente di terminare un processo che esegue una funzione di loop.
MatthewToday

10
Il multitasking è cooperativo, non preventivo. Una goroutine in un ciclo non entra mai nello scheduler, quindi non può mai essere uccisa.
Jeff Allen

34

Non puoi uccidere una goroutine dall'esterno. Puoi segnalare a una goroutine di smettere di usare un canale, ma sulle goroutine non c'è alcuna possibilità di eseguire alcun tipo di meta-gestione. I Goroutine hanno lo scopo di risolvere i problemi in modo cooperativo, quindi ucciderne uno che si comporta male non sarebbe quasi mai una risposta adeguata. Se vuoi l'isolamento per la robustezza, probabilmente vuoi un processo.


E potresti voler esaminare il pacchetto encoding / gob, che consentirebbe a due programmi Go di scambiare facilmente strutture di dati su un pipe.
Jeff Allen

Nel mio caso, ho una goroutine che verrà bloccata su una chiamata di sistema e devo dirgli di interrompere la chiamata di sistema e quindi uscire. Se fossi bloccato su un canale letto, sarebbe possibile fare come da te suggerito.
Omnifario

Ho già visto quel problema. Il modo in cui abbiamo "risolto" è stato aumentare il numero di thread all'inizio dell'applicazione in modo che corrispondesse al numero di goroutine che avrebbero potuto + il numero di CPU
rouzier

20

In generale, potresti creare un canale e ricevere un segnale di stop nella goroutine.

Ci sono due modi per creare il canale in questo esempio.

  1. canale

  2. contesto . Nell'esempio farò una democontext.WithCancel

La prima demo, usa 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")
}

La seconda demo, usa 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")
}

11

So che questa risposta è già stata accettata, ma ho pensato di buttare dentro i miei 2 centesimi. Mi piace usare il pacchetto tomb . È fondamentalmente un canale di chiusura potenziato, ma fa anche cose carine come restituire eventuali errori. La routine sotto controllo ha ancora la responsabilità di verificare i segnali di kill remoto. Non è possibile ottenere un "id" di una goroutine e ucciderla se si comporta in modo anomalo (cioè: bloccato in un ciclo infinito).

Ecco un semplice esempio che ho testato:

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)
}

L'output dovrebbe essere simile a:

# Loop the loop
# Loop the loop
# Loop the loop
# Loop the loop
# Death from above

Questo pacchetto è piuttosto interessante! Hai testato per vedere cosa tombfa con la goroutine nel caso in cui succeda qualcosa al suo interno che getta il panico, per esempio? Tecnicamente parlando, la goroutine esce in questo caso, quindi presumo che chiamerà ancora il differito proc.Tomb.Done()...
Gwyneth Llewelyn

1
Ciao Gwyneth, sì, proc.Tomb.Done()verrebbe eseguito prima che il panico blocchi il programma, ma a che scopo? È possibile che la goroutine principale abbia una finestra di opportunità molto piccola per eseguire alcune istruzioni, ma non ha modo di riprendersi da un panico in un'altra goroutine, quindi il programma continua a bloccarsi. I documenti dicono: "Quando la funzione F chiama panico, l'esecuzione di F si interrompe, tutte le funzioni differite in F vengono eseguite normalmente, e quindi F ritorna al suo chiamante .. Il processo continua sullo stack finché tutte le funzioni nella goroutine corrente non sono tornate, a quel punto il programma va in crash. "
Kevin Cantwell

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.