Como parar um goroutine

102

Eu tenho uma goroutine que chama um método e passa o valor retornado em um canal:

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

Como faço para impedir tal goroutine?

Łukasz Gruner
fonte
1
Outra resposta, dependendo da sua situação, é usar um contexto Go. Não tenho tempo ou conhecimento para criar uma resposta sobre isso. Eu só queria mencioná-lo aqui para que as pessoas que pesquisam e acham essa resposta insatisfatória tenham outro tópico para puxar (trocadilho intencional). Na maioria dos casos, você deve fazer o que a resposta aceita sugere. Esta resposta menciona contextos: stackoverflow.com/a/47302930/167958
Omnifário

Respostas:

51

EDIT: Eu escrevi esta resposta às pressas, antes de perceber que sua pergunta é sobre como enviar valores para um chan dentro de uma goroutine. A abordagem abaixo pode ser usada com um canal adicional, conforme sugerido acima, ou usando o fato de que o canal que você já possui é bidirecional, você pode usar apenas um ...

Se sua goroutine existe apenas para processar os itens que saem do canal, você pode usar o "close" embutido e o formulário especial de recepção para canais.

Ou seja, quando terminar de enviar itens ao canal, você o fecha. Então, dentro de sua goroutine, você obtém um parâmetro extra para o operador de recepção que mostra se o canal foi fechado.

Aqui está um exemplo completo (o grupo de espera é usado para garantir que o processo continue até que a goroutine seja concluída):

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
fonte
16
O corpo da goroutine interna é escrito de forma mais linguística usando deferto call wg.Done()e um range chloop para iterar sobre todos os valores até que o canal seja fechado.
Alan Donovan de
115

Normalmente, você passa pela goroutine um canal de sinal (possivelmente separado). Esse canal de sinal é usado para enviar um valor para quando você quiser que o goroutine pare. A goroutine faz pesquisas nesse canal regularmente. Assim que detecta um sinal, ele fecha.

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

// Do stuff

// Quit goroutine
quit <- true
Jimt
fonte
26
Não esta bom o suficiente. E se a goroutine ficar presa em um loop infinito devido a um bug?
Elazar Leibovich
232
Então o bug deve ser corrigido.
jimt
13
Elazar, O que você sugere é uma maneira de interromper uma função depois de chamá-la. Uma goroutine não é um thread. Ele pode ser executado em um thread diferente ou pode ser executado no mesmo thread que o seu. Não conheço nenhuma linguagem que apóie o que você acha que Go deveria apoiar.
Jeremy Wall
5
@jeremy Não discordando do Go, mas Erlang permite que você mate um processo que está executando uma função de loop.
MatthewToday
10
Go multitarefa é cooperativo, não preventivo. Uma goroutine em um loop nunca entra no agendador, portanto, nunca pode ser eliminada.
Jeff Allen
34

Você não pode matar um goroutine de fora. Você pode sinalizar a uma goroutine para parar de usar um canal, mas não há controle sobre as goroutines para fazer qualquer tipo de meta-gerenciamento. Goroutines se destinam a resolver problemas de forma cooperativa, portanto, matar alguém que esteja se comportando mal quase nunca seria uma resposta adequada. Se você deseja isolamento para obter robustez, provavelmente deseja um processo.

SteveMcQwark
fonte
E você pode querer dar uma olhada no pacote encoding / gob, que permitiria que dois programas Go trocassem facilmente estruturas de dados por um tubo.
Jeff Allen
No meu caso, tenho uma goroutine que será bloqueada em uma chamada de sistema e preciso dizer a ela para abortar a chamada de sistema e depois sair. Se eu fosse bloqueado em um canal lido, seria possível fazer o que você sugere.
Omnifarious
Eu vi esse problema antes. A maneira como "resolvemos" era aumentar o número de threads no início do aplicativo para coincidir com o número de goroutines que poderiam + o número de CPUs
rouzier
20

Geralmente, você pode criar um canal e receber um sinal de parada no goroutine.

Há duas maneiras de criar um canal neste exemplo.

  1. canal

  2. contexto . No exemplo vou demonstrarcontext.WithCancel

A primeira demonstração, use 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")
}

A segunda demonstração, 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
fonte
11

Sei que essa resposta já foi aceita, mas pensei em jogar meus 2 centavos. Gosto de usar o pacote de tumba . É basicamente um canal de saída atualizado, mas também faz coisas boas, como devolver quaisquer erros. A rotina sob controle ainda tem a responsabilidade de verificar os sinais de eliminação remota. Afaik, não é possível obter um "id" de um goroutine e matá-lo se ele estiver se comportando mal (ou seja: preso em um loop infinito).

Aqui está um exemplo simples que testei:

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

A saída deve ser semelhante a:

# Loop the loop
# Loop the loop
# Loop the loop
# Loop the loop
# Death from above
Kevin Cantwell
fonte
Este pacote é bastante interessante! Você testou para ver o que tombfaz com a goroutine caso aconteça alguma coisa dentro dela que cause pânico, por exemplo? Tecnicamente falando, o goroutine sai neste caso, então presumo que ele ainda chamará o adiado proc.Tomb.Done()...
Gwyneth Llewelyn
1
Oi Gwyneth, sim proc.Tomb.Done()seria executado antes que o pânico travasse o programa, mas para quê? É possível que a goroutine principal tenha uma janela de oportunidade muito pequena para executar algumas instruções, mas não há como se recuperar de um pânico em outra goroutine, então o programa ainda trava. Os documentos dizem: "Quando a função F chama o pânico, a execução de F é interrompida, todas as funções adiadas em F são executadas normalmente e, em seguida, F retorna para seu chamador .. O processo continua na pilha até que todas as funções na goroutine atual tenham retornado, ponto em que o programa trava. "
Kevin Cantwell