go言語 並列実行入門
Go言語の並列実行について学んだメモです。
func goroutine(s string, wg *sync.WaitGroup){ defer wg.Done() for i := 0; i < 5; i++{ fmt.Println(s) } } func normal (s string){ for i := 0; i < 5; i++{ fmt.Println(s) } } func main(){ var wg sync.WaitGroup wg.Add(1) go goroutine("world", &wg) normal("hello") wg.Wait() }
並列実行は、time.Sleepしないと並列実行している関数が実行される前にmain関数が終了しプログラムが終わることがある。それを防ぐためにsync.WaitGroupを使う wg.Add(1)をしたら同じ数wg.Done()を呼ばないとエラーになる
goroutineを使って並列実行するが、これらは別のプログラムなのでreturnで値を返せない。そのため、channelを使って同期させる。
func goruoutine1(s []int, c chan int){ sum := 0 for _, v := range s{ sum += v } c <- sum } func main(){ s := []int{1, 2, 3, 4, 5} c := make(chan int) go goroutine1(s, c) x := <-c fmt.Println(x) }
mainからスライスのsとchanのcを送って、goroutineが走る。x:=<-cの部分でgoroutineのc<-sumが終わるまで止まる。 chanを複数作ってもいいし、1つのchanを複数の関数で使ってもよい。順番はキューの順番になる。
func main(){ ch := make(chan int, 2) ch <- 100 fmt.Println(len(ch)) ch <- 200 fmt.Println(len(ch)) ch <-300 fmt.Println(len(ch)) }
はエラーになる。make(chan int, 2)の2はバッファで、chの最大個数を示す。 よって、2で定義したのに3つ目を入れようとしたのでエラーになる。 取り出しているとエラーにならない。取り出すのは
s := <- ch
という記法。
func main()){ ch := make(chan int, 2) ch <- 100 fmt.Println(len(ch)) ch <- 200 fmt.Println(len(ch)) close(ch) for c := range ch{ fmt.Println(c) } }
chanを取り出すときにfor range文を使いたいが、close(ch)を書かずに使うとエラーになる。バッファの個数以上取り出そうとするため。close(ch)を書いていればエラーにならない
func goruoutine1(s []int, c chan int){ sum := 0 for _, v := range s{ sum += v } c <- sum } func main(){ s := []int{1, 2, 3, 4, 5} c := make(chan int) go goroutine1(s, c) x := <-c fmt.Println(x) }
これの、計算の途中過程を逐次表示するようにしたいなら、
func goruoutine1(s []int, c chan int){ sum := 0 for _, v := range s{ sum += v c <- sum } close(c) } func main(){ s := []int{1, 2, 3, 4, 5} c := make(chan int, len(s)) go goroutine1(s, c) for i := range c{ fmt.Println(i) } }
close(c)はエラー防止。chanの数をメモリに確保したいならmake(chan int, len(s))とする。
func producer(ch chan int, i int){ //Something ch <- i * 2 } func consumer(ch chan int, wg *sync.WaitGroup){ for i := range ch{ func (){ defer wg.Done() fmt.Println("process", i * 1000) }() } } func main(){ var wg sync.WaitGroup ch := make(chan int) //producer for i := 0; i < 10; i++{ wg.Add(1) go producer(ch, i) } //consumer go consumer(ch, &wg) wg.Wait //すべてのWaitGroupが終わるまで待つ close(ch) }
close(ch)がこの位置にあるのは、consumerのforループがまだchanがあると思って待っているため。 それを終わらせるためにclose(ch)をする。
package main import ( "fmt" "time" ) func main() { tick := time.Tick(100 * time.Millisecond) boom := time.After(500 * time.Millisecond) //time.tickとtime.Afterはchを返す関数。 for { select { case <-tick: //返ってくるchを変数に入れなくてもよい。入れると時刻も出る fmt.Println("tick.") case <-boom: //返ってくるchを変数に入れなくてもよい。入れると時刻も出る fmt.Println("BOOM") return default: fmt.Println(" .") time.Sleep(50 * time.Millisecond) } } fmt.Println("#########") }
for select文は、複数のチャネルを受信してチャネルによって処理を変えられる。selectは、for文の中にあるからずっと回る。for文がないと止まる。終わりたいなら、for文をbreakする 最後のfmt.Printlnはこのままだと実行されない。先にmain関数が終わってしまうため。実行したいなら、
OuterLoop2 for{ select{ case <- tick://返ってくるchを変数に入れなくてもよい。入れると時刻も出る fmt.Println("tick.") case <-boom://返ってくるchを変数に入れなくてもよい。入れると時刻も出る fmt.Println("BOOM") break OuterLoop2 default: fmt.Println(" .") time.Sleep(50*time.Millisecond) } } fmt.Println("#########")
のようにして、ラベルを使ってfor文の上に出る。名前は何でもいい。
package main import ( "fmt" "sync" "time" ) func main() { c := make(map[string]int) go func() { for i := 0; i < 10; i++ { c["key"] += 1 } }() go func() { for i := 0; i < 10; i++ { c["key"] += 1 } }() time.Sleep(1 * time.Second) fmt.Println(c, c["key"]) }
このコードだと、成功したりエラーを出したりする。両方のゴルーチンがcへの書き込みをしており、cへの書き込みが同時になるとエラーになる。よって、sync, mutexを使う。
package main import ( "fmt" "sync" "time" ) type Counter struct { v map[string]int mux sync.Mutex } func (c *Counter) Inc(key string) { c.mux.Lock() defer c.mux.Unlock() c.v[key]++ } func (c *Counter) Value(key string) int { c.mux.Lock() defer c.mux.Unlock() return c.v[key] } func main() { c := Counter{v: make(map[string]int)} go func() { for i := 0; i < 10; i++ { //c["key"] += 1 c.Inc("Key") } }() go func() { for i := 0; i < 10; i++ { //c["key"] += 1 c.Inc("Key") } }() time.Sleep(1 * time.Second) fmt.Println(c, c.Value("Key")) }