web-dev-qa-db-fra.com

comment arrêter un goroutine

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

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

Comment puis-je arrêter un tel goroutine?

81
Łukasz Gruner

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

Si votre goroutine existe uniquement pour traiter les éléments qui sortent du canal, vous pouvez utiliser la commande "fermer" et le formulaire de réception spécial pour les chaînes.

C'est-à-dire qu'une fois que vous avez fini d'envoyer des éléments sur le canal, vous le fermez. Ensuite, dans votre goroutine, vous recevez un paramètre supplémentaire pour l'opérateur de réception indiquant si le canal a été fermé.

Voici un exemple complet (le groupe d’attente sert à s’assurer que le processus se poursuit jusqu’à ce que la goroutine soit terminée):

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

En général, vous transmettez à la goroutine un canal de signal (éventuellement séparé). Ce canal de signal est utilisé pour insérer une valeur lorsque vous voulez que le goroutine s’arrête. Le goroutine interroge cette chaîne 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
100
jimt

Vous ne pouvez pas tuer un goroutine de l'extérieur. Vous pouvez indiquer à un goroutine de ne plus utiliser de canal, mais les goroutines ne peuvent gérer aucune méta-gestion. Les Goroutines sont destinées à résoudre les problèmes de manière coopérative, donc en tuer un qui se comporte mal ne constituerait presque jamais une réponse adéquate. Si vous souhaitez que l'isolation soit robuste, vous souhaitez probablement un processus.

25
SteveMcQwark

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

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

  1. canal

  2. contexte. Dans l'exemple, je vais démo context.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")
}
9
zouying

Je sais que cette réponse a déjà été acceptée, mais je pensais jeter mes 2cents. J'aime utiliser le package tomb . Il s’agit en fait d’un canal arrêté, mais il fait des choses agréables, comme le renvoi d’erreurs. La routine sous contrôle a toujours la responsabilité de vérifier les signaux d'abattage à distance. Après tout, il n'est pas possible d'obtenir un "identifiant" de goroutine et de le tuer s'il se comporte mal (c'est-à-dire 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
8
Kevin Cantwell

Personnellement, j'aimerais utiliser la gamme sur une chaîne dans un goroutine:

https://play.golang.org/p/qt48vvDu8cd

Dave a écrit un excellent article à ce sujet: http://dave.cheney.net/2013/04/30/curious-channels .

5
mikespook