lelelemon’s blog

カメの歩みでのんびり学んでいます。

【Go言語】channel で非同期に並列処理を行い、結果を取得する

Go で時間のかかる処理を並列で実行するのに channel という仕組みがあることを知ったので試しました。

 

channel とは

goroutine間で値を送受信するための機構(ChatGPTより)。

 

 

goroutine は非同期に処理を並行実行できるものの、処理が終了するとそのまま破棄されてしまうため、

非同期処理を行ってレスポンスをやり取りしたい場合は channel を使用する必要があるようです。

 

以降でこの処理を検証します。

 

検証用のコードを用意

func heavyProdess(num int) string {
    time.Sleep(1 * time.Second)
    return fmt.Sprintf("finish %d", num)
}

 

シンプルに、1秒スリープして文字列を返す関数です。

channelを使わない場合(同期)

func main() {
    start := time.Now()
    for i := 0; i < 5; i++ {
        fmt.Println(heavyProdess(i + 1))
    }
    end := time.Now()

    fmt.Println("処理時間:", (end.Sub(start).Seconds()))
}

 

これを実行すると下記の結果です。

go run main.go 
finish 1
finish 2
finish 3
finish 4
finish 5
処理時間: 5.068204847

 

順に同期的に実行していくため、全て実行するのにループ回数分、およそ5秒かかりました。

channelを使う場合(非同期)

続いて、channel を使って処理を並列実行するようにしてみます。

下記のようにコードを書き換えました。

 

func heavyProdess(num int, wg *sync.WaitGroup, ch chan<- string) {
    defer wg.Done()
    time.Sleep(1 * time.Second)
    ch <- fmt.Sprintf("finish %d", num)
}

func main() {
    ch := make(chan string)
    var wg sync.WaitGroup
    start := time.Now()

    for i := 0; i < 5; i++ {
        wg.Add(1)
        go heavyProdess(i+1, &wg, ch)
    }

    go func() {
        wg.Wait()
        close(ch)
    }()

    for result := range ch {
        fmt.Println(result)
    }
    end := time.Now()

    fmt.Println("処理時間:", (end.Sub(start).Seconds()))
}

 

(コードの解説)

WaitGroup を使用して、goroutine をまとめて実行するようにしています。

上記 WaitGroup の説明を抜粋し、ChatGPT で和訳↓

A WaitGroup waits for a collection of goroutines to finish. The main goroutine calls Add to set the number of goroutines to wait for. Then each of the goroutines runs and calls Done when finished. At the same time, Wait can be used to block until all goroutines have finished.

 

WaitGroup は、一連のゴルーチンの終了を待機します。メインのゴルーチンは Add を呼び出して待機するゴルーチンの数を設定します。その後、各ゴルーチンが実行され、終了時に Done を呼び出します。同時に、Wait を使用してすべてのゴルーチンが終了するまでブロックすることができます。

 

WaitGroup でgoroutineをまとめて並列実行することができるため、

並列実行したgoroutineがそれぞれ channel に値を送信するようにし、最終的に channel から値を取り出す形にしています。

 

heavyProcess 関数
  • defer wg.Done()で WaitGroup の待機カウントを1減らす
  • channel に処理結果を送信
main 関数
  • wg.Add(1) で WaitGroup の待機カウントを1増やす
  • go func 無名関数内の wg.Wait() で WaitGroup のすべての goroutine が完了するのを待ち合わせ、すべて完了したら channel を閉じる
  • for 句で channel から処理結果を取得、表示

これを実行すると下記の結果です。

go run main.go 
finish 5
finish 2
finish 3
finish 4
finish 1
処理時間: 1.001226728

 

同期処理だと約5秒かかっていたのが、channelを使った非同期処理だと約1秒に短縮されました。

 

以上サンプルです。

使いこなせればchannelは強力な武器になるので、ぜひマスターしたいです。