https://qiita.com/castaneai/items/7815f3563b256ae9b18d

Goにはgoroutineという特定の関数を非同期で実行させられる便利な機能があります。 しかし、名前通り非同期処理なので実装によっては「裏でずっと無駄に動いていた」「気づいたら裏で増殖していた」といった事態になります。

Goに限らずあらゆるプログラムで言えることですが、 一度起動した非同期処理は終了までちゃんと面倒を見る ことが重要です。 もう少し具体的な例でいうと非同期処理はキャンセル(中断)を考慮しよう、というものが挙げられます。 本記事ではGoの非同期処理システムであるgoroutineを止める方法を解説していきます。

goroutineを止める方法はない?

いきなり結論からですが、goroutineを止める専用のAPIは存在しません。

では、どうすればgoroutineを止められるのでしょうか? 答えは簡単で goroutineで起動した関数をreturnして終わらせるだけです。 「いや、そんなことはわかってるよ!」って感じですね。 そういうことではなく、多くの人が知りたいのは 裏で動いているgoroutineをメインのスレッドからいい感じに中断させるにはどうすればいいか? だと思います。

ループしているgoroutineを止める

たとえば、次のように無限ループで何かを処理し続けるgoroutineがあるとします。 これをメイン処理から止めたい場合どうすればいいでしょうか?

package main

import (
    "time"
)

func main() {
    go loop()

    // 何かの処理

    // ここで loop() を止めたい

    time.Sleep(3 * time.Second)
    println("finish")
}

// 無限ループする関数
func loop() {
    for {
        println("loop...")
        time.Sleep(1 * time.Second)
    }
}

channelを使って中止を通知する

別のgoroutineに何かを通知したい場合、定番なのは channel を使う方法です。 流れとしては次のようになります。

  1. 中断通知用のchannelを作る
  2. goroutineに作ったchannelを渡す
  3. メイン処理からchannelを使って通知を送る(止まれ!)
  4. goroutineの中で通知を受け取ったら return で関数を終わらせる(止まった)
package main

import (
    "time"
)

func main() {
    cancel := make(chan struct{})
    go loop(cancel)

    // 何かの処理

    // ここで loop() を止める
    close(cancel)

    time.Sleep(3 * time.Second)
    println("finish")
}

// 無限ループする関数
func loop(cancel chan struct{}) {
    for {
        select {
        case <-cancel:
            // 中断通知が来た
            println("cancel")
            return
        default:
            println("loop...")
            time.Sleep(1 * time.Second)
        }

    }
}

この実装にはいくつかポイントがあります。