もなかアイスの試食品

「とりあえずやってみたい」そんな気持ちが先走りすぎて挫折が多い私のメモ書きみたいなものです.

Go言語のゴルーチン・チャネルの使い方メモ

はじめに

何回かGo言語で処理を作ったことがあったけど、Go言語で並行処理を一切書いたことがなかったので、勉強がてら簡単な処理を書いてみることにした。

参考サイト

qiita.com

環境

並行処理しない

とりあえず並行処理していないコードを書いた

package sample

import (
    "fmt"
    "log"
    "time"
)

func NotUseGoRoutine() {
    log.Println("---------- Start Sample 01 ----------")
    process("first ", 10)
    process("second", 10)
    process("third ", 10)
    log.Println("----------  End Sample 01  ----------")
}

func process(name string, loopCount int) {
    commonLoopMessage(name, loopCount)
}

func commonLoopMessage(name string, loopCount int)  {
    for count := 1; count <= loopCount; count++ {
        time.Sleep(time.Second / 10)
        fmt.Printf("name: %s, loopCount: %d\n", name, count)
    }
}

出力

2021/05/28 06:49:38 ---------- Start Sample 01 ----------
name: first , loopCount: 1
name: first , loopCount: 2
name: first , loopCount: 3
name: first , loopCount: 4
name: first , loopCount: 5
name: first , loopCount: 6
name: first , loopCount: 7
name: first , loopCount: 8
name: first , loopCount: 9
name: first , loopCount: 10
name: second, loopCount: 1
name: second, loopCount: 2
name: second, loopCount: 3
name: second, loopCount: 4
name: second, loopCount: 5
name: second, loopCount: 6
name: second, loopCount: 7
name: second, loopCount: 8
name: second, loopCount: 9
name: second, loopCount: 10
name: third , loopCount: 1
name: third , loopCount: 2
name: third , loopCount: 3
name: third , loopCount: 4
name: third , loopCount: 5
name: third , loopCount: 6
name: third , loopCount: 7
name: third , loopCount: 8
name: third , loopCount: 9
name: third , loopCount: 10
2021/05/28 06:49:41 ----------  End Sample 01  ----------

並行処理する

さっきのコードを並行処理するように書き換える

並行処理が終わる前に関数を抜けないように、チャネルを使い、並行処理が終わるまでブロックする

参考サイトを見つつ修正

func UseGoRoutine01() {
    log.Println("---------- Start Sample 02 ----------")
    ch1 := make(chan bool)
    ch2 := make(chan bool)
    ch3 := make(chan bool)

    go processWithCh01(ch1, "first ", 10)
    go processWithCh01(ch2, "second", 10)
    go processWithCh01(ch3, "third ", 10)

    <- ch1
    fmt.Println("Receive ch1 message")
    <- ch2
    fmt.Println("Receive ch2 message")
    <- ch3
    fmt.Println("Receive ch3 message")

    log.Println("----------  End Sample 02  ----------")
}

func processWithCh01(ch chan bool, name string, loopCount int) {
    commonLoopMessage(name, loopCount)
    ch <- true
}

出力

2021/05/28 06:49:41 ---------- Start Sample 02 ----------
name: second, loopCount: 1
name: third , loopCount: 1
name: first , loopCount: 1
name: second, loopCount: 2
name: third , loopCount: 2
name: first , loopCount: 2
name: third , loopCount: 3
name: second, loopCount: 3
name: first , loopCount: 3
name: third , loopCount: 4
name: first , loopCount: 4
name: second, loopCount: 4
name: second, loopCount: 5
name: third , loopCount: 5
name: first , loopCount: 5
name: first , loopCount: 6
name: second, loopCount: 6
name: third , loopCount: 6
name: third , loopCount: 7
name: second, loopCount: 7
name: first , loopCount: 7
name: first , loopCount: 8
name: third , loopCount: 8
name: second, loopCount: 8
name: third , loopCount: 9
name: second, loopCount: 9
name: first , loopCount: 9
name: first , loopCount: 10
Receive ch1 message
name: second, loopCount: 10
Receive ch2 message
name: third , loopCount: 10
Receive ch3 message
2021/05/28 06:49:42 ----------  End Sample 02  ----------

ちょっと修正

チャネルは破棄できるらしい。(チャネルの戻り値を無視することではなく、チャネルをメモリから破棄的な意味)

チャネル破棄について調べると以下のサイトを発見

hori-ryota.com

チャネルを使う場合、以下の注意点があるらしい(他にもあるだろうけど)

  • 破棄したチャネルにイベントを送ると panic する
  • チャネルを多重に破棄すると panic する
  • 受信が先にいなくなるとブロックし続ける

また「処理が終了したこと」のみを通知する場合、チャネルに使用する型は「struct{}」で良いらしい

「struct{}」のサイズが「0」なので、通知のデータサイズが無いことを明示できる

qiita.com

func UseGoRoutine02() {
    log.Println("---------- Start Sample 03 ----------")
    ch1 := make(chan struct{})
    ch2 := make(chan struct{})
    ch3 := make(chan struct{})

    go processWithCh02(ch1, "first ", 10)
    go processWithCh02(ch2, "second", 10)
    go processWithCh02(ch3, "third ", 10)

    <- ch1
    fmt.Println("Receive ch1 message")
    <- ch2
    fmt.Println("Receive ch2 message")
    <- ch3
    fmt.Println("Receive ch3 message")

    log.Println("----------  End Sample 03  ----------")
}

func processWithCh02(ch chan struct{}, name string, loopCount int) {
    commonLoopMessage(name, loopCount)
    close(ch)
}

出力

2021/05/28 06:49:42 ---------- Start Sample 03 ----------
name: second, loopCount: 1
name: first , loopCount: 1
name: third , loopCount: 1
name: second, loopCount: 2
name: first , loopCount: 2
name: third , loopCount: 2
name: third , loopCount: 3
name: second, loopCount: 3
name: first , loopCount: 3
name: first , loopCount: 4
name: third , loopCount: 4
name: second, loopCount: 4
name: second, loopCount: 5
name: first , loopCount: 5
name: third , loopCount: 5
name: third , loopCount: 6
name: second, loopCount: 6
name: first , loopCount: 6
name: first , loopCount: 7
name: third , loopCount: 7
name: second, loopCount: 7
name: second, loopCount: 8
name: first , loopCount: 8
name: third , loopCount: 8
name: third , loopCount: 9
name: first , loopCount: 9
name: second, loopCount: 9
name: third , loopCount: 10
name: second, loopCount: 10
name: first , loopCount: 10
Receive ch1 message
Receive ch2 message
Receive ch3 message
2021/05/28 06:49:43 ----------  End Sample 03  ----------