Как остановить горутину

103

У меня есть горутина, которая вызывает метод и передает возвращаемое значение по каналу:

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

Как мне остановить такую ​​горутину?

Лукаш Грюнер
источник
1
Другой ответ, в зависимости от вашей ситуации, - использовать Go Context. У меня нет времени или знаний, чтобы дать ответ по этому поводу. Я просто хотел упомянуть об этом здесь, чтобы люди, которые ищут и находят этот ответ неудовлетворительным, могли потянуть еще одну тему (каламбур). В большинстве случаев вы должны делать то, что предлагает принятый ответ. В этом ответе упоминаются контексты: stackoverflow.com/a/47302930/167958
Omnifarious

Ответы:

52

РЕДАКТИРОВАТЬ: Я написал этот ответ в спешке, прежде чем понял, что ваш вопрос касается отправки значений чану внутри горутины. Приведенный ниже подход можно использовать либо с дополнительным каналом, как предложено выше, либо с учетом того факта, что канал, который у вас уже есть, является двунаправленным, вы можете использовать только один ...

Если ваша горутина существует исключительно для обработки элементов, выходящих из чана, вы можете использовать встроенную функцию «закрыть» и специальную форму приема для каналов.

То есть, как только вы закончите отправлять элементы на чан, вы закрываете его. Затем внутри вашей горутины вы получаете дополнительный параметр для оператора приема, который показывает, был ли канал закрыт.

Вот полный пример (группа ожидания используется, чтобы убедиться, что процесс продолжается до завершения горутины):

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
Тело внутренней горутины более идиоматично написано с использованием deferto call wg.Done()и range chцикла для перебора всех значений, пока канал не будет закрыт.
Алан Донован
116

Обычно вы передаете горутине (возможно, отдельный) сигнальный канал. Этот сигнальный канал используется для ввода значения, когда вы хотите, чтобы горутина остановилась. Горутина регулярно опрашивает этот канал. Как только он обнаруживает сигнал, он закрывается.

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

// Do stuff

// Quit goroutine
quit <- true
удар
источник
26
Не достаточно хорош. Что, если горутина застряла в бесконечном цикле из-за ошибки?
Элазар Лейбович
232
Тогда ошибка должна быть исправлена.
jimt
13
Элазар, вы предлагаете способ остановить функцию после того, как вы ее вызвали. Горутина - это не поток. Он может работать в другом потоке или в том же потоке, что и ваш. Я не знаю языка, который поддерживает то, что, по вашему мнению, должен поддерживать Go.
Джереми Уолл
5
@jeremy Не возражаю для Go, но Erlang позволяет убить процесс, в котором выполняется функция цикла.
MatthewToday
10
Многозадачность - это совместная работа, а не вытеснение. Горутина в цикле никогда не попадает в планировщик, поэтому ее нельзя убить.
Джефф Аллен
34

Вы не можете убить горутину извне. Вы можете сигнализировать горутине о прекращении использования канала, но у горутины нет ручки для какого-либо управления метаданными. Горутины предназначены для совместного решения проблем, поэтому убийство плохо себя ведет почти никогда не будет адекватным ответом. Если вам нужна изоляция для устойчивости, вам, вероятно, нужен процесс.

Стив МакКварк
источник
И вы можете изучить пакет encoding / gob, который позволит двум программам Go легко обмениваться структурами данных по каналу.
Джефф Аллен
В моем случае у меня есть горутина, которая будет заблокирована при системном вызове, и мне нужно сказать ей, чтобы она прервала системный вызов и затем вышла. Если бы меня заблокировали при чтении канала, можно было бы поступить так, как вы предлагаете.
Omnifarious
Я видел эту проблему раньше. Способ, которым мы «решили» это, заключался в том, чтобы увеличить количество потоков в начале приложения, чтобы оно соответствовало количеству горутин, которые могли бы + количество процессоров
Rouzier
20

Как правило, вы можете создать канал и получить стоп-сигнал в горутине.

В этом примере есть два способа создать канал.

  1. канал

  2. контекст . В примере я продемонстрируюcontext.WithCancel

Первая демонстрация, используйте 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")
}

Вторая демонстрация, используйте 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
источник
11

Я знаю, что этот ответ уже принят, но я подумал, что брошу свои 2 цента. Мне нравится использовать гробницу пакет . По сути, это усовершенствованный канал выхода, но он также делает приятные вещи, например, возвращает любые ошибки. Подпрограмма, находящаяся под контролем, по-прежнему отвечает за проверку сигналов удаленного отключения. Afaik невозможно получить "id" горутины и убить ее, если она плохо себя ведет (то есть: застряла в бесконечном цикле).

Вот простой пример, который я тестировал:

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

Результат должен выглядеть так:

# Loop the loop
# Loop the loop
# Loop the loop
# Loop the loop
# Death from above
Кевин Кэнтуэлл
источник
Этот пакет довольно интересный! Вы проверяли, что tombделает горутина, если внутри нее что-то происходит, например, вызывая панику? Технически говоря, в этом случае горутина завершается, поэтому я предполагаю, что она все равно будет вызывать отложенный proc.Tomb.Done()...
Гвинет Ллевелин
1
Привет, Гвинет, да, proc.Tomb.Done()будет выполнено до того, как паника приведет к сбою программы, но с какой целью? Возможно, что у основной горутины может быть очень небольшое окно возможностей для выполнения некоторых операторов, но у нее нет способа восстановиться после паники в другой горутине, поэтому программа все равно вылетает. Документы говорят: «Когда функция F вызывает панику, выполнение F останавливается, любые отложенные функции в F выполняются нормально, а затем F возвращается к вызывающему. Процесс продолжается вверх по стеку, пока не вернутся все функции в текущей горутине, в этот момент программа вылетает ".
Кевин Кэнтуэлл