Comment arrêter une goroutine

102

J'ai un goroutine qui appelle une méthode et passe la valeur retournée sur un canal:

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

Comment arrêter une telle goroutine?

Łukasz Gruner
la source
1
Une autre réponse, en fonction de votre situation, est d'utiliser un Go Context. Je n'ai ni le temps ni les connaissances nécessaires pour créer une réponse à ce sujet. Je voulais juste le mentionner ici pour que les personnes qui recherchent et trouvent cette réponse insatisfaisante aient un autre fil à tirer (jeu de mots). Dans la plupart des cas, vous devez faire ce que suggère la réponse acceptée. Cette réponse mentionne les contextes: stackoverflow.com/a/47302930/167958
Omnifarious

Réponses:

51

EDIT: J'ai écrit cette réponse à la hâte, avant de réaliser que votre question porte sur l'envoi de valeurs à un chan dans un goroutine. L'approche ci-dessous peut être utilisée soit avec un chan supplémentaire comme suggéré ci-dessus, soit en utilisant le fait que le chan que vous avez déjà est bidirectionnel, vous pouvez utiliser juste celui ...

Si votre goroutine existe uniquement pour traiter les éléments sortant du chan, vous pouvez utiliser la fonction intégrée "close" et le formulaire de réception spécial pour les chaînes.

Autrement dit, une fois que vous avez terminé d'envoyer des éléments sur le chan, vous le fermez. Ensuite, à l'intérieur de votre goroutine, vous obtenez un paramètre supplémentaire pour l'opérateur de réception qui indique si le canal a été fermé.

Voici un exemple complet (le waitgroup est utilisé pour s'assurer que le processus continue jusqu'à ce que la goroutine se termine):

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
la source
16
Le corps de la goroutine interne est écrit de manière plus idiomatique en utilisant deferto call wg.Done(), et une range chboucle pour itérer sur toutes les valeurs jusqu'à ce que le canal soit fermé.
Alan Donovan
115

En règle générale, vous passez la goroutine par un canal de signal (éventuellement séparé). Ce canal de signal est utilisé pour insérer une valeur lorsque vous souhaitez que le goroutine s'arrête. Les sondages goroutine qui canalisent régulièrement. Dès qu'il détecte un signal, il se ferme.

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

// Do stuff

// Quit goroutine
quit <- true
Jimt
la source
26
Pas assez bon. Et si le goroutine est coincé dans une boucle sans fin, à cause d'un bug?
Elazar Leibovich le
232
Ensuite, le bogue devrait être corrigé.
jimt
13
Elazar, ce que vous suggérez est un moyen d'arrêter une fonction après l'avoir appelée. Un goroutine n'est pas un fil. Il peut s'exécuter dans un thread différent ou dans le même thread que le vôtre. Je ne connais aucun langage qui prend en charge ce que vous pensez que Go devrait prendre en charge.
Jeremy Wall
5
@jeremy Pas en désaccord pour Go, mais Erlang vous permet de tuer un processus qui exécute une fonction de bouclage.
MatthewToday
10
Le multitâche est coopératif et non préventif. Un goroutine dans une boucle n'entre jamais dans le planificateur, donc il ne peut jamais être tué.
Jeff Allen
34

Vous ne pouvez pas tuer un goroutine de l'extérieur. Vous pouvez signaler à un goroutine d'arrêter d'utiliser un canal, mais il n'y a pas de poignée sur les goroutines pour faire une sorte de méta-gestion. Les goroutines sont destinées à résoudre les problèmes de manière coopérative, donc tuer celui qui se comporte mal ne serait presque jamais une réponse adéquate. Si vous voulez une isolation pour la robustesse, vous voulez probablement un processus.

SteveMcQwark
la source
Et vous voudrez peut-être examiner le paquet encoding / gob, qui permettrait à deux programmes Go d'échanger facilement des structures de données via un tube.
Jeff Allen
Dans mon cas, j'ai un goroutine qui sera bloqué sur un appel système, et je dois lui dire d'abandonner l'appel système puis de quitter. Si j'étais bloqué sur une chaîne lue, il serait possible de faire ce que vous suggérez.
Omnifarious
J'ai déjà vu ce problème. La façon dont nous l'avons "résolu" était d'augmenter le nombre de threads au démarrage de l'application pour correspondre au nombre de goroutines qui pourraient éventuellement + le nombre de CPU
rouzier
20

Généralement, vous pouvez créer un canal et recevoir un signal d'arrêt dans le goroutine.

Il existe deux façons de créer un canal dans cet exemple.

  1. canal

  2. contexte . Dans l'exemple, je vais faire une démonstrationcontext.WithCancel

La première démo, utilisez 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 deuxième démo, utilisez 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
la source
11

Je sais que cette réponse a déjà été acceptée, mais j'ai pensé jeter mes 2cents dedans. J'aime utiliser le paquet tomb . C'est fondamentalement un canal d'arrêt remplacé, mais il fait également de bonnes choses comme renvoyer les erreurs. La routine sous contrôle a toujours la responsabilité de vérifier les signaux d'arrêt à distance. Afaik il n'est pas possible d'obtenir un "identifiant" d'un goroutine et de le tuer s'il se comporte mal (ie: coincé dans une boucle infinie).

Voici un exemple simple que j'ai testé:

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 sortie devrait ressembler à:

# Loop the loop
# Loop the loop
# Loop the loop
# Loop the loop
# Death from above
Kevin Cantwell
la source
Ce package est assez intéressant! Avez-vous testé pour voir ce que tombfait la goroutine au cas où quelque chose se passerait à l'intérieur et semerait la panique, par exemple? Techniquement parlant, le goroutine sort dans ce cas, donc je suppose qu'il appellera toujours le différé proc.Tomb.Done()...
Gwyneth Llewelyn
1
Salut Gwyneth, oui proc.Tomb.Done()serait exécuté avant que la panique plante le programme, mais à quelle fin? Il est possible que le goroutine principal ait une très petite fenêtre d'opportunité pour exécuter certaines instructions, mais il n'a aucun moyen de se remettre d'une panique dans un autre goroutine, donc le programme plante toujours. Les documents disent: "Lorsque la fonction F appelle panique, l'exécution de F s'arrête, toutes les fonctions différées de F sont exécutées normalement, puis F retourne à son appelant .. Le processus continue dans la pile jusqu'à ce que toutes les fonctions de la goroutine actuelle soient retournées, à quel moment le programme se bloque. "
Kevin Cantwell