並行処理でchannelを実装するプログラムの動きを確認する

Go言語
この記事は約8分で読めます。
Go初学者
Go初学者

Go言語の並行処理でchannelを実装した簡単なプログラムを見てみたいな…

この記事ではchannelを実装した並行処理の基本が理解できます!
  • 簡単なプログラムでchannelを実装した並行処理の動きを確認できます
  • for range を使用してchannelの値を取り出すプログラムを確認できます
  • close() 関数について

Go言語の並行処理でchannelを実装したプログラムをご紹介いたします。

今回紹介するプログラムは簡単なものですが、channelの役割を理解できるプログラムとなっております。

Go言語らしいプログラムとなっていますのでお楽しみに!

もりぴ
この記事を書いた人

XHTML1.0時代にHTML&CSSを勉強した経験あり。無趣味だった私が2020年5月からプログラミング学習を開始し現在も挫折せずに趣味で学習を楽しんでいる51歳。プログラミングの楽しさをブログを通してお伝えしていきます。

もりぴをフォローする

channelの役割を再確認しよう!

channelのデータ送受信の記事でもお伝えしましたが、channelは複数のgoroutine間でデータの受け渡しをするために設計されたデータ構造です。

複数のgoroutine間でのデータ受け渡しイメージ

channelを通さないとデータの送受信ができずに、それぞれのgoroutineが与えられた単純な処理をするだけの並行処理となってしまいます。

gorutine並行処理イメージ

channelの役割を確認していきましょう。

channelを実装したGo言語の並行処理

では、実際にプログラムでGo言語の並行処理にchannelを実装していきます。

goroutinが1つchannelが1つのプログラム

ここで紹介するのは「goroutinが1つchannelが1つのプログラム」になります。

goroutinが1つchannelが1つのプログラムイメージ

プログラムはスライスの値の合計値を求めるものです。

関数の引数としてスライスとchannelを設定し、合計値の計算結果をchannelに送信して受信した値を表示するプログラムです。

// goroutinが1つchannelが1つのプログラム
package main

import "fmt"

// 引数にスライスとchannel
func parallel1(sl[]int, ch chan int){
    // 引数の合計値を求める処理
    sum := 0
    // インデックス番号は不要なので「_」
    for _, v := range sl{
	sum += v
    }
    // 計算した結果をchannelに送信
    ch <- sum
}

func main() {
    // 引数で使用するスライス
    sl := []int{10, 20, 30, 40, 50}

    // channelを定義
    ch := make(chan int)

    // goroutineで処理
    go parallel1(sl, ch)

    // 変数aにchannelの値を受信
    a := <-ch
    fmt.Println("sumの値は", a)
}
// 出力結果
$ go run main.go 
sumの値は 150

ここで重要なのは、合計値 sum の計算結果が終了しchannelの ch に値が送信され、変数achannelの値を受信されるまでプログラムの処理は進まないことです。

もりぴ
もりぴ

このプログラムではわかりにくいので、次のプログラムで確認しましょう。

goroutinが2つchannelが1つのプログラム

先程のプログラムを活用して「goroutinが2つchannelが1つのプログラム」の動きを確認していきます。

goroutinが2つchannelが1つのプログラムイメージ

プログラムは先程と同じスライスの合計値を求める同じ関数を2つ用意して、goroutinを2つで並行処理するものです。

// goroutinが2つchannelが1つのプログラム
package main

import "fmt"

func parallel1(sl[]int, ch chan int){
    sum := 0
    for _, v := range sl{
	sum += v
    }
    ch <- sum
}

func parallel2(sl[]int, ch chan int){
    sum := 0
    for _, v := range sl{
	sum += v
    }
    ch <- sum
}

func main() {
    // 引数で使用するスライス
    sl := []int{10, 20, 30, 40, 50}
    sl2 := []int{1, 2, 3, 4, 5}

    // channelを定義
    ch := make(chan int)

    // goroutineで処理
    go parallel1(sl, ch)
    go parallel2(sl2, ch)

    // 変数aにchannelの値を受信
    a := <-ch
    fmt.Println("sumの値は", a)

    // 変数bにchannelの値を受信
    b := <-ch
    fmt.Println("sumの値は", b)
}
// 出力結果
$ go run main.go
sumの値は 15
sumの値は 150

このプログラムでは、a := <-ch に値が受信されてから fmt.Println("sumの値は", a) が実行されるまで、b := <-ch は処理を待ってくれています。

ですから、sync.WaitGroup の処理をしなくてもプログラムは正常終了するのです。

そして、プログラムを見ると順次処理的な考えであれば 変数ago parallel1(sl, ch) の計算結果が受信されるイメージがありますが、並行処理のためどちらのgoroutineの処理が受信されるかはわかりません。

for rangeを使用してchannelの値を取り出す

先程のプログラムではスライスの合計値だけを出力しましたが、合計値の途中結果を随時表示するにはfor文と range を使用する必要があります。

そして、channelを閉じる操作というのが必要になるので close() 関数についてもお伝えしていきます。

for rangeを使用してchannelの値を取り出すプログラム

では、プログラムを確認していきましょう。

// rangeを使用してchannelの値を取り出す
package main

import "fmt"

// 引数にスライスとchannel
func parallel3(sl[]int, ch chan int){
    sum := 0
    for _, v := range sl{
	sum += v
        // 計算の途中結果を送信
        ch <- sum
    }
    // close() でchannelの終了を知らせる
    close(ch)
}

func main() {
    // 引数で使用するスライス
    sl := []int{10, 20, 30, 40, 50}

    // len()でスライスの要素数をバッファに設定
    ch1 := make(chan int, len(sl))
    go parallel3(sl, ch1)

    // ch1の値を受信する
    for i := range ch1 {
        fmt.Println(i)
    }
}
// 出力結果
$ go run main.go
10
30
60
100
150
もりぴ
もりぴ

問題なくスライスの合計値の途中結果が出力されています。

ポイントとなるのが ch <- sum の場所で、sum += v の計算結果が即送信されます。

計算結果の受信には for range で受け取り画面に出力します。

close() 関数の役割

ここで問題になるのがデータの受信側が送られるデータの数がわかりません。

きちんと「もうデータは無いから for range のプログラムは終了していいよ」と教えなければいけません。

その役割を担うのが close() 関数です。

もし、先程のプログラムで close() 関数が無いと…

// 出力結果
$ go run main.go
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
        /home/morip/go/src/golangstart/channel/chan_heikou/main.go:61 +0x3cd
exit status 2

deadlock!になり「すべてのゴルーチンがスリープ状態」というエラーになります。

channelを使用する際は、あなたが開発するプログラムで close() 関数が必要になるのかを検討する必要があります。

【まとめ】並行処理でchannelを実装するプログラム

今回はGo言語の並行処理で、channelを実装するプログラムを確認していいただきました。

それでは、まとめにはいりましょう。

この記事で押さえておくべきこと!
  • channelは複数のgoroutine間でデータの受け渡しをするために設計されたデータ構造であることを再確認
  • channelの値を受信されるまでプログラムの処理は進まない
  • for range を使用してchannelの値を取り出すことができる
  • close() 関数の役割

以上です。

もりぴ
もりぴ

難しかったでしょうか?

エディタで実際にコードを写経してみて動きを確認してみてください。

そして、コードを改変してみても面白いですよ。

comment

タイトルとURLをコピーしました