
Go言語の並行処理でchannelを実装した簡単なプログラムを見てみたいな…
Go言語の並行処理でchannelを実装したプログラムをご紹介いたします。
今回紹介するプログラムは簡単なものですが、channelの役割を理解できるプログラムとなっております。
Go言語らしいプログラムとなっていますのでお楽しみに!
channelの役割を再確認しよう!
channelのデータ送受信の記事でもお伝えしましたが、channelは複数のgoroutine間でデータの受け渡しをするために設計されたデータ構造です。

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

channelの役割を確認していきましょう。
channelを実装したGo言語の並行処理
では、実際にプログラムでGo言語の並行処理にchannelを実装していきます。
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
に値が送信され、変数a
にchannelの値を受信されるまでプログラムの処理は進まないことです。

このプログラムではわかりにくいので、次のプログラムで確認しましょう。
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 の処理をしなくてもプログラムは正常終了するのです。
そして、プログラムを見ると順次処理的な考えであれば 変数a
に go 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を実装するプログラムを確認していいただきました。
それでは、まとめにはいりましょう。
以上です。

難しかったでしょうか?
エディタで実際にコードを写経してみて動きを確認してみてください。
そして、コードを改変してみても面白いですよ。
comment