ちょっとうろうろ

思いつくままちらほらと

goroutine内の変数の扱い方

goroutine内の変数の扱い方

 

goroutineがまだうまく扱えないので、go func()内で変数がどういう処理をされるのかを調べてみる。環境によって出力順序は異なると思うので、その点は目をつぶって欲しい。

ベース

generate()関数から送信された数値を受信し、そのまま出力する極めて簡単な処理を行う。

main1.go

package main

import "fmt"

//数値の生成
func generate() chan int {
    out := make(chan int)
    
    for i:=0; i<10; i++ {
        go func() {
            out <- i
        }()
    }
    
    return out
}

//数値の出力
func main(){
    ch := generate()
    for i:=0; i<10; i++ {
        fmt.Println(<-ch)
    }
}

i番目の値を送信するだけの簡単な関数だ。

なお、この関数をそのまま実行すると、

output1
10
10
10
10
10
10
10
10
10
10

と出力される。
これは、go func()内のiに値が割り当てられないまま最後までgoroutineをスタックし続け、forを抜けたところで初めて値が割り当てられるためだろう。

ちゃんと1から10までの値を出力するためには、go func()に引数を与えてやる必要がある。

main2.go

func generate() chan int {
    out := make(chan int)
    
    for i:=0; i<10; i++ {
        go func(n int) {
            out <- n
        }(i)
    }
}
output2
2
0
1
5
4
8
6
9
3
7

並列実行なので順番はバラバラになる。

これを順番通りに出力するには、for文の外でカウンタ変数を設ける必要がある。
また送信より先にインクリメントしないと処理されない。

main3.go

func generate() chan int {
    out := make(chan int)
    c := -1
    
    for i:=0; i<10; i++ {
        go func() {
            c++
            out <- c
        }()
    }
}
output3
0
1
2
3
4
5
6
7
8
9

つまり、並列でも良い場合は内部カウンタ、順列がいい場合は外部カウンタにする必要がある。
……まぁ、後者はgoroutineの利点を捨ててる気もするけど

補足

generate()のforを多めに回すと、送信される値がまた違ってくる。

main4.go

func generate() chan int {
    out := make(chan int)
    
    for i:=0; i<=10; i++ {
        go func(n int) {
            out <- n
        }(i)
    }
}
output4
10
4
0
1
2
3
6
8
9
5

並列操作をするときに数値を間違えると大変なことになるということだ。