Jak zatrzymać gorutynę

102

Mam goroutine, który wywołuje metodę i przekazuje zwróconą wartość na kanale:

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

Jak zatrzymać taką gorutynę?

Łukasz Gruner
źródło
1
Inną odpowiedzią, w zależności od sytuacji, jest użycie kontekstu Go. Nie mam czasu ani wiedzy, aby odpowiedzieć na to pytanie. Chciałem tylko wspomnieć o tym tutaj, aby ludzie, którzy szukają i uważają tę odpowiedź za niezadowalającą, mają inny wątek do wyciągnięcia (gra słów zamierzona). W większości przypadków należy postępować tak, jak sugeruje zaakceptowana odpowiedź. Ta odpowiedź wspomina o kontekstach: stackoverflow.com/a/47302930/167958
Omnifarious,

Odpowiedzi:

51

EDYCJA: Napisałem tę odpowiedź w pośpiechu, zanim zdałem sobie sprawę, że twoje pytanie dotyczy wysyłania wartości do chan wewnątrz goroutine. Poniższe podejście można zastosować z dodatkowym kanałem, jak zasugerowano powyżej, lub korzystając z faktu, że kanał, który już masz, jest dwukierunkowy, możesz użyć tylko jednego ...

Jeśli twój goroutine istnieje wyłącznie po to, aby przetwarzać elementy wychodzące z kanału, możesz skorzystać z wbudowanego „zamknij” i specjalnego formularza odbioru dla kanałów.

Oznacza to, że kiedy skończysz wysyłać elementy na kanale, zamykasz go. Następnie wewnątrz swojego goroutine otrzymujesz dodatkowy parametr do operatora odbioru, który pokazuje, czy kanał został zamknięty.

Oto pełny przykład (grupa waitgroup służy do upewnienia się, że proces będzie kontynuowany do zakończenia gorutyny):

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
źródło
16
Ciało wewnętrznej gorutyny jest bardziej idiomatycznie napisane za pomocą deferwywołania wg.Done()i range chpętli do iteracji po wszystkich wartościach, aż kanał zostanie zamknięty.
Alan Donovan,
115

Zwykle podaje się goroutine (prawdopodobnie oddzielny) kanał sygnałowy. Ten kanał sygnałowy jest używany do wpychania wartości, kiedy chcesz, aby goroutine się zatrzymał. Sondaże goroutine, które regularnie przekazują kanał. Gdy tylko wykryje sygnał, kończy pracę.

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

// Do stuff

// Quit goroutine
quit <- true
jimt
źródło
26
Nie wystarczająco dobre. Co jeśli goroutine utknie w nieskończonej pętli z powodu błędu?
Elazar Leibovich
232
Następnie błąd powinien zostać naprawiony.
jimt
13
Elazar, sugerujesz sposób na zatrzymanie funkcji po jej wywołaniu. Goroutine to nie nić. Może działać w innym wątku lub może działać w tym samym wątku co Twój. Nie znam żadnego języka, który wspierałby to, co Twoim zdaniem Go powinno wspierać.
Jeremy Wall
5
@jeremy Nie zgadzam się z Go, ale Erlang pozwala zabić proces, który wykonuje funkcję pętli.
MatthewToday
10
Wielozadaniowość Go to współpraca, a nie zapobieganie. Goroutine w pętli nigdy nie wchodzi do programu planującego, więc nigdy nie można go zabić.
Jeff Allen
34

Nie możesz zabić gorutyny z zewnątrz. Możesz zasygnalizować goroutine, aby przestał używać kanału, ale nie ma uchwytu na goroutines, który mógłby wykonać jakiekolwiek zarządzanie meta. Goroutyny mają na celu wspólne rozwiązywanie problemów, więc zabicie kogoś, kto źle się zachowuje, prawie nigdy nie byłoby odpowiednią reakcją. Jeśli chcesz izolacji w celu zapewnienia niezawodności, prawdopodobnie potrzebujesz procesu.

SteveMcQwark
źródło
I możesz zajrzeć do pakietu encoding / gob, który pozwoliłby dwóm programom Go łatwo wymieniać struktury danych przez potok.
Jeff Allen
W moim przypadku mam gorutynę, która zostanie zablokowana podczas wywołania systemowego i muszę jej powiedzieć, aby przerwała wywołanie systemowe, a następnie zakończyła. Gdybym został zablokowany na kanale odczytu, dałoby się zrobić to, co sugerujesz.
Omnifarious
Widziałem ten problem wcześniej. Sposób, w jaki "rozwiązaliśmy" to zwiększenie liczby wątków na początku aplikacji, aby dopasować liczbę goroutines, które mogłyby być + liczba procesorów
rouzier
20

Ogólnie rzecz biorąc, można utworzyć kanał i odebrać sygnał stopu w gorutynę.

W tym przykładzie istnieją dwa sposoby tworzenia kanału.

  1. kanał

  2. kontekst . W przykładzie pokażęcontext.WithCancel

Pierwsze demo, użyj 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")
}

Drugie demo, użyj 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
źródło
11

Wiem, że ta odpowiedź została już zaakceptowana, ale pomyślałem, że wrzucę do niej swoje 2 centy. Lubię korzystać z pakietu grobowca . Jest to w zasadzie superwizowany kanał wyjścia, ale robi też fajne rzeczy, takie jak przekazywanie wszelkich błędów. Kontrolowana procedura nadal odpowiada za sprawdzanie sygnałów zdalnego zabijania. Afaik, nie jest możliwe uzyskanie „identyfikatora” gorutyny i zabicie go, jeśli źle się zachowuje (np. Utknął w nieskończonej pętli).

Oto prosty przykład, który przetestowałem:

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

Wynik powinien wyglądać następująco:

# Loop the loop
# Loop the loop
# Loop the loop
# Loop the loop
# Death from above
Kevin Cantwell
źródło
Ten pakiet jest całkiem interesujący! Czy przetestowałeś, co tombrobi z gorutyną na wypadek, gdyby coś się w niej wydarzyło, na przykład wywołując panikę? Technicznie rzecz biorąc, wyjść goroutine w tym przypadku, więc jestem zakładając, że będzie nadal nazywają odroczony proc.Tomb.Done()...
Gwyneth Llewelyn
1
Cześć Gwyneth, tak proc.Tomb.Done(), uruchomiłoby się, zanim panika zawiesi program, ale po co? Jest możliwe, że główna gorutyna może mieć bardzo małe okno na wykonanie niektórych instrukcji, ale nie ma sposobu na wyjście z paniki w innej gorutyce, więc program nadal się zawiesza. Dokumenty mówią: „Kiedy funkcja F wywołuje panikę, wykonywanie F zatrzymuje się, wszelkie odroczone funkcje w F są wykonywane normalnie, a następnie F wraca do swojego wywołującego… Proces kontynuuje na stosie, aż wszystkie funkcje w bieżącym gorutynie powrócą, w tym momencie program ulega awarii. ”
Kevin Cantwell,