ゴルーチンを止める方法


102

私はメソッドを呼び出し、チャネルに戻り値を渡すゴルーチンを持っています:

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

このようなゴルーチンを止めるにはどうすればいいですか?


1
状況に応じて、もう1つの答えはGoコンテキストを使用することです。これについての答えを作成する時間や知識がありません。私がここでそれを述べたかったので、この答えを満足せずに検索して見つけた人々は、別のスレッドを引きます(しゃれた意図)。ほとんどの場合、受け入れられた回答が示唆するように行う必要があります。この回答はコンテキストに言及しています:stackoverflow.com/a/47302930/167958
Omnifarious

回答:


50

編集: 私はあなたの質問がgoroutine内のchanに値を送信することに関するものであることを理解する前に、急いでこの回答を書きました。以下のアプローチは、上記のように追加のchanで使用するか、すでに持っているchanが双方向であるという事実を使用して、1つだけを使用することができます...

chanから出てくるアイテムを処理するためだけにゴルーチンが存在する場合は、組み込みの "close"ビルトインとチャネル用の特別な受信フォームを利用できます。

つまり、ちゃんにアイテムを送信し終えたら、ちゃんと閉じます。次に、goroutine内で、チャネルが閉じているかどうかを示す追加パラメーターをreceiveオペレーターに取得します。

以下は完全な例です(ウェイトグループは、ゴルーチンが完了するまでプロセスが続行することを確認するために使用されます)。

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

通常、goroutineに(場合によっては別の)信号チャネルを渡します。その信号チャネルは、ゴルーチンを停止させたいときに値をプッシュするために使用されます。goroutineはそのチャネルを定期的にポーリングします。シグナルを検出するとすぐに終了します。

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

// Do stuff

// Quit goroutine
quit <- true

26
十分じゃない。バグが原因で、ゴルーチンが無限ループに陥った場合はどうなりますか?
Elazar Leibovich、2011

232
その後、バグを修正する必要があります。
11

13
Elazar、あなたが提案するのは、関数を呼び出した後に関数を停止する方法です。ゴルーチンはスレッドではありません。別のスレッドで実行することも、同じスレッドで実行することもできます。Goがサポートする必要があると思われる言語をサポートしている言語はありません。
ジェレミーウォール、

5
@jeremy Goに異論はありませんが、Erlangではループ関数を実行しているプロセスを強制終了できます。
MatthewToday

10
マルチタスクに行くことは、先制ではなく協調的です。ループ内のゴルーチンはスケジューラに入らないため、強制終了することはできません。
ジェフアレン

34

外からゴルーチンを殺すことはできません。チャネルの使用を停止するようにゴルーチンに信号を送ることはできますが、ゴルーチンにはなんらかのメタ管理を行うためのハンドルがありません。ゴルーチンは協調的に問題を解決することを目的としているため、動作に問題のあるものを殺しても、適切な対応はほとんどありません。堅牢性のための分離が必要な場合は、おそらくプロセスが必要です。


また、encoding / gobパッケージを調べると、2つのGoプログラムでパイプを介してデータ構造を簡単に交換できます。
ジェフアレン

私の場合、システムコールでブロックされるgoroutineがあり、システムコールを中止して終了するように指示する必要があります。チャンネルの読み取りでブロックされた場合、あなたが提案したように実行することができます。
18

以前にその問題を見ました。私たちが「解決」した方法は、アプリケーションの開始時にスレッドの数を増やして、可能性のあるゴルーチンの数+ CPUの数と一致させることでした
rouzier

18

一般に、チャネルを作成して、goroutineで停止信号を受信できます。

この例では、チャネルを作成する方法が2つあります。

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

2番目のデモでは、次を使用します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

私はこの答えは、すでに承認されているけど、私は私は私の2centsを投げると思った。私が使用したい墓のパッケージを。これは基本的には終了したquitチャネルですが、エラーを返すなどの素晴らしいことも行います。制御下にあるルーチンは、リモートkillシグナルをチェックする責任があります。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()...
Gwyneth Llewelyn

1
こんにちはグウィネス、はいproc.Tomb.Done()、パニックがプログラムをクラッシュさせる前に実行されますが、何のために?メインのゴルーチンには、いくつかのステートメントを実行する非常に小さな機会がある可能性がありますが、別のゴルーチンのパニックから回復する方法がないため、プログラムは引き続きクラッシュします。ドキュメントは言う:「関数Fがパニックを呼び出すと、Fの実行が停止し、Fのすべての遅延関数が正常に実行され、Fは呼び出し元に戻ります。プロセスは、現在のゴルーチンのすべての関数が戻るまでスタックを続けます。その時点でプログラムはクラッシュします。」
Kevin Cantwell、2017

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.