Як зупинити гороутин


102

У мене є програма, яка викликає метод і передає повернене значення на каналі:

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

Як я зупиняю таку goroutine?


1
Ще одна відповідь, залежно від вашої ситуації, - це використовувати контекст Go. У мене немає часу і знань, щоб створити відповідь на це. Я просто хотів згадати це тут, щоб люди, які шукають та вважають цю відповідь незадовільною, мали ще одну нитку, яку слід витягнути (каламбур призначений). У більшості випадків слід робити так, як пропонує прийнята відповідь. Ця відповідь згадує контексти: stackoverflow.com/a/47302930/167958
всілякий

Відповіді:


50

EDIT: Цю відповідь я написав поспіхом, перш ніж зрозуміти, що ваше питання стосується надсилання значень до чану всередині програми. Підхід нижче можна використовувати або з додатковим чаном, як було запропоновано вище, або використовуючи той факт, що чан, який у вас уже є, є двонаправленим, ви можете використовувати лише один ...

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

Тобто, як тільки ви закінчите надсилати елементи на чан, ви закриєте його. Потім всередині вашої програми ви отримуєте додатковий параметр оператору прийому, який показує, чи закритий канал.

Ось повний приклад (група очікувань використовується для того, щоб переконатися, що процес триває до завершення програми goroutut):

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

15
Тіло внутрішньої програми є ідіоматично записаним за допомогою deferвиклику wg.Done(), і range chцикл перебирає всі значення, поки канал не буде закритий.
Алан Донован

115

Як правило, ви передаєте в програму (можливо, окремий) сигнал-канал. Цей сигнальний канал використовується для введення значення, коли ви хочете, щоб програма зупинилася. Горутіна регулярно опитує цей канал. Як тільки він виявить сигнал, він закривається.

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

// Do stuff

// Quit goroutine
quit <- true

26
Недостатньо хороший. Що робити, якщо goroutut застряг у нескінченному циклі через помилку?
Елазар Лейбович

232
Потім помилку слід виправити.
jimt

13
Елазар, що ви пропонуєте - це спосіб зупинити функцію після того, як ви її зателефонували. Горутіна - це не нитка. Він може працювати в іншому потоці, або він може працювати в тому ж потоці, що і ваш. Я не знаю жодної мови, яка б підтримувала те, що, на вашу думку, вважає, що Go має підтримувати.
Джеремі Уолл

5
@jeremy Не погоджується з Go, але Erlang дозволяє вбити процес, який виконує циклічну функцію.
MatthewToday

10
Перехід на багатозадачність є спільним, а не превентивним. Горопрограма в циклі ніколи не потрапляє до планувальника, тому її ніколи не можна вбити.
Джефф Аллен

34

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


І ви можете заглянути в пакет кодування / gob, який дозволить двом програмам Go легко обмінюватися структурами даних по трубі.
Джефф Аллен

У моєму випадку у мене є програма, яка буде заблокована під час системного виклику, і мені потрібно сказати, щоб вона припинила системний виклик і потім вийшла. Якби мене заблокували на каналі читання, можна було б зробити так, як ви запропонували.
Всезнайко

Я бачив це питання раніше. Як ми «вирішили», це збільшити кількість потоків на початку програми, щоб відповідати кількості goututin, що можливо + кількості процесорів
rouzier

19

Як правило, ви можете створити канал і отримати сигнал зупинки в програмі.

У цьому прикладі є два способи створення каналу.

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

11

Я знаю, що ця відповідь вже прийнята, але я думав, що я кину свої 2 центи. Мені подобається використовувати гробницю . Це, в основному, канал, який вийшов, але він робить приємні речі, як передавати будь-які помилки. Програма, що контролюється, все ще несе відповідальність за перевірку сигналів віддаленого вбивства. Афаїку неможливо отримати "ідентифікатор" програми і вбити його, якщо він не поводиться (тобто: застряг у нескінченному циклі).

Ось простий приклад, який я перевірив:

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()...
Gwyneth Llewelyn

1
Привіт, Гвінет, так proc.Tomb.Done(), виконували б до того, як паніка програє програму, але з якою метою? Можливо, що в головній програмі може бути дуже маленьке вікно можливостей для виконання деяких операцій, але вона не має можливості відновитись з паніки в іншій програмі, тому програма все одно виходить з ладу. Документи кажуть: "Коли функція F викликає паніку, виконання F зупиняється, будь-які відкладені функції у F виконуються нормально, а потім F повертається до свого виклику. Процес продовжує стек до тих пір, поки всі функції поточної програми не повернуться, в який момент програма виходить з ладу. "
Кевін Кантвелл

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.