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?


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

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

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.


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

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

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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.