Cómo detener una gorutina

102

Tengo una goroutine que llama a un método y pasa el valor devuelto en un canal:

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

¿Cómo detengo tal gorutina?

Łukasz Gruner
fuente
1
Otra respuesta, dependiendo de su situación, es utilizar un contexto de Go. No tengo el tiempo ni el conocimiento para crear una respuesta sobre esto. Solo quería mencionarlo aquí para que las personas que busquen y encuentren esta respuesta insatisfactoria tengan otro hilo para tirar (juego de palabras). En la mayoría de los casos, debe hacer lo que sugiere la respuesta aceptada. Esta respuesta menciona contextos: stackoverflow.com/a/47302930/167958
Omnifarious

Respuestas:

50

EDITAR: Escribí esta respuesta apresuradamente, antes de darme cuenta de que su pregunta es sobre enviar valores a un chan dentro de una goroutine. El siguiente enfoque se puede usar con un chan adicional como se sugirió anteriormente, o usando el hecho de que el chan que ya tiene es bidireccional, puede usar solo el uno ...

Si su goroutine existe únicamente para procesar los elementos que salen del canal, puede hacer uso del "cerrar" incorporado y del formulario de recepción especial para canales.

Es decir, una vez que hayas terminado de enviar elementos al chan, lo cierras. Luego, dentro de su goroutine, obtiene un parámetro adicional para el operador de recepción que muestra si el canal se ha cerrado.

Aquí hay un ejemplo completo (el grupo de espera se usa para asegurarse de que el proceso continúe hasta que se complete la 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()
}
latigazo
fuente
15
El cuerpo de la goroutine interna está escrito de manera más idiomática usando deferto call wg.Done()y un range chbucle para iterar sobre todos los valores hasta que se cierra el canal.
Alan Donovan
115

Normalmente, pasa la goroutine por un canal de señal (posiblemente separado). Ese canal de señal se utiliza para introducir un valor cuando desee que se detenga la gorutina. Las encuestas goroutine que canalizan habitualmente. Tan pronto como detecta una señal, se cierra.

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

// Do stuff

// Quit goroutine
quit <- true
Jimt
fuente
26
No es suficiente. ¿Qué pasa si la goroutine se atasca en un bucle sin fin debido a un error?
Elazar Leibovich
232
Entonces el error debería corregirse.
jimt
13
Elazar, lo que sugieres es una forma de detener una función después de haberla llamado. Una goroutine no es un hilo. Puede ejecutarse en un hilo diferente o puede ejecutarse en el mismo hilo que el suyo. No conozco ningún idioma que admita lo que usted cree que Go debería admitir.
Jeremy Wall
5
@jeremy No estoy en desacuerdo con Go, pero Erlang le permite matar un proceso que está ejecutando una función de bucle.
MatthewToday
10
La multitarea es cooperativa, no preventiva. Una goroutine en un bucle nunca ingresa al programador, por lo que nunca se puede eliminar.
Jeff Allen
34

No se puede matar una gorutina desde fuera. Puede indicarle a una goroutine que deje de usar un canal, pero no hay control de goroutines para hacer ningún tipo de metagestión. Las gorutinas están destinadas a resolver problemas de manera cooperativa, por lo que matar a uno que se está portando mal casi nunca sería una respuesta adecuada. Si desea aislamiento por robustez, probablemente desee un proceso.

SteveMcQwark
fuente
Y es posible que desee examinar el paquete encoding / gob, que permitiría que dos programas Go intercambien fácilmente estructuras de datos a través de una tubería.
Jeff Allen
En mi caso, tengo una goroutine que se bloqueará en una llamada al sistema y necesito decirle que anule la llamada al sistema y luego salga. Si estuviera bloqueado en una lectura de canal, sería posible hacer lo que sugieres.
Omnifarious
Vi ese problema antes. La forma en que lo "resolvimos" fue aumentar el número de subprocesos al inicio de la aplicación para que coincida con el número de goroutines que posiblemente podrían + el número de CPU
rouzier
18

Generalmente, puede crear un canal y recibir una señal de parada en la goroutine.

Hay dos formas de crear un canal en este ejemplo.

  1. canal

  2. contexto . En el ejemplo haré una demostracióncontext.WithCancel

La primera demostración, 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 segunda demostración, use 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
fuente
11

Sé que esta respuesta ya ha sido aceptada, pero pensé en tirar mis 2 centavos. Me gusta usar el paquete de la tumba . Es básicamente un canal para dejar de fumar mejorado, pero también hace cosas buenas como devolver cualquier error. La rutina bajo control todavía tiene la responsabilidad de verificar las señales de apagado remoto. Afaik no es posible obtener un "id" de una goroutine y matarla si se está portando mal (es decir, atascada en un bucle infinito).

Aquí hay un ejemplo simple que probé:

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

La salida debería verse así:

# Loop the loop
# Loop the loop
# Loop the loop
# Loop the loop
# Death from above
Kevin Cantwell
fuente
¡Este paquete es bastante interesante! ¿Has probado para ver qué tombhace con la goroutine en caso de que ocurra algo dentro de ella que provoque pánico, por ejemplo? Técnicamente hablando, la goroutine sale en este caso, así que supongo que todavía llamará al diferido proc.Tomb.Done()...
Gwyneth Llewelyn
1
Hola Gwyneth, sí proc.Tomb.Done()lo ejecutaría antes de que el pánico colapse el programa, pero ¿con qué fin? Es posible que la goroutine principal tenga una ventana de oportunidad muy pequeña para ejecutar algunas sentencias, pero no tiene forma de recuperarse de un pánico en otra goroutine, por lo que el programa aún falla. Los doc momento en el que el programa se bloquea ".
Kevin Cantwell