Go言語(プログラミング)入門メモ⑲

## ゴルーチン間で値を送受信する
プログラムにおいて、値を送信する関数などをProducer、受信する処理をConsumerと呼ぶことがある。
Goで複数のゴルーチンにProducerとConsumerの役割を持たせた場合の並行処理のプログラムについて説明する。

## ProducerとConsumerのゴルーチンを作成する
main関数からProducerのゴルーチンをいくつか並行で走らせて実行し。結果をチャネルに入れる。チャネルからConsumerのゴルーチンに渡して、データの処理を実行する。
イメージ図
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3579705/852a0cd5-3f60-f329-e57d-237ff12ea137.png)

使用例としては、様々なサーバからログの解析結果をProducer側で処理し、Consumer側に渡してログの処理や保存をするようなアプリケーションなどがある。

## Producer処理の作成
引数iに2をかけるという処理をしてチャネルに送信するproducer関数を作成する。
~~~
func producecr(ch chan int, i int) {
    //Something
    ch <- i *2
}
~~~

## Consumer処理の作成
producer関数のゴルーチンから受け取った値を処理するconsumer関数を作成する。
チャネルの値をrangeで取り出して処理をする。ここでは「process」という文字列と、iに1000を掛けた値を表示する。
また、処理が終わったら、producer関数から渡された値の処理が終わったということを示すためにwg.Done()を実行する。
~~~
func consumer(ch chan int, wg *sync.WaitGroup) {
    for i := range ch {
        fmt.Println("process", i*1000)
        wg.Done()
    }
}
~~~

## main関数の処理
main関数では、まずsync.WaitGroupを宣言する。また、チャネルを作成する。
続いて、producer関数をfor文で10回呼び出す。ループの中でConsumerに値を渡すときにすべてを渡せたかを確認するため、「wg.Add(1)」を書く。その後、ゴルーチンを呼び出してproducer関数を実行する。
次に、consumer関数の引数にチャネルとWaitGroupを渡して実行する。その後、consumer関数がproducer関数から送られてくる値をすべて受け取るまで「wg.Wait()」で待つ。最後に「close(ch)」でチャネルを閉じる。
~~~
func main() {
    var wg sync.WaitGroup
    ch:= make(chan int)

    // Produger
    for i :=0; i < 10; i++ {
        wg.Add(1)
        go producer(ch, i)
    }

    // Consumer
    go consumer (ch, &wg)
    wg.Wait()
    close(ch)
}

//以下のように表示される
process 0
process 4000
process 2000
process 6000
process 8000
process 14000
process 10000
process 12000
process 16000
process 18000
~~~

producer関数のゴルーチンを呼び出す際に「wg.Add(1)」を10回実行したのでconsumer関数のゴルーチンで「wg.Done()」が10回実行したのでconsumer関数のゴルーチンで「wg.Done()」が10回実行されるまで、main関数の「wg.Wait()」で待ち続ける。

## ゴルーチンでrangeがチャネルを待ち続けてしまう場合
consumer関数のゴルーチンでは、チャネルに値が送信されてくるのをrangeで待ち続けてしまうので、main関数の最後に「close(ch)」を実行してチャネルを閉じる。
consumer関数のfor文の外に「fmt.Println("#####")」を書いてみるだけで実行しても「#####」は表示されない。
~~~
func consumer(ch chan int, wg *sync.WaitGroup) {
    for i := range ch {
        fmt.Println("process", i*1000)
        wg.Done()
    }
    fmt.Println("#####")
}
~~~
これは、consumer関数の「fmt.Println("#####")」を行う前にコードが終了してしまうため。この解消のためにmain関数のconsumerの実行部分のclose(ch)の下に、2秒間待つ処理を書いて、終わったら「Done」と表示させる。
~~~
// Consumer
    go consumer (ch, &wg)
    wg.Wait()
    close(ch)
    time.Sleep(2 * time.Second) 
    fmt.Println("Done")

//以下のように表示される
process 0
process 4000
process 2000
process 6000
process 8000
process 14000
process 10000
process 12000
process 16000
process 18000
#####
Done
~~~

## 学習に使用した教材
・『入門】Golang基礎入門 + 各種ライブラリ + 簡単なTodoWebアプリケーション開発(Go言語)』M.A EduTech
https://www.udemy.com/course/golang-webgosql/?utm_medium=udemyads&utm_source=bene-msa&utm_campaign=responsive&utm_content=top-1&utm_term=general&msclkid=81e2f24a32cc185d275d953d60760226&couponCode=NEWYEARCAREERJP

・『シリコンバレー一流プログラマーが教える Goプロフェッショナル大全』酒井 潤 (著)
https://www.amazon.co.jp/%E3%82%B7%E3%83%AA%E3%82%B3%E3%83%B3%E3%83%90%E3%83%AC%E3%83%BC%E4%B8%80%E6%B5%81%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9E%E3%83%BC%E3%81%8C%E6%95%99%E3%81%88%E3%82%8B-Go%E3%83%97%E3%83%AD%E3%83%95%E3%82%A7%E3%83%83%E3%82%B7%E3%83%A7%E3%83%8A%E3%83%AB%E5%A4%A7%E5%85%A8-%E9%85%92%E4%BA%95-%E6%BD%A4/dp/4046070897