- Goで並行処理する時の初級編です
- 今回の話はGoを書いている人には簡単すぎますが、並行処理やGoの経験がない人には難しすぎると思います
- 少しでも考え方に慣れると並行処理が楽しくなってきます
- 実はgoroutineが分からないという人は見たことがないですが、channelは多くの人がつまずくポイントです
Goで並行処理をしたい場合はgoroutineを使う。使い方は簡単で関数の前にgo
と書くだけ。
go MyFunc()
// 無名関数で処理を書くこともよくやる
go func() {//
// 色々処理を書く
}()
本当にこれだけで並行処理ができます。これで並行処理が書けます!!!
……これで終われば話は簡単なのですが、これで終わりません。
package main
import (
"fmt"
"time"
)
func main() {
go func() {
fmt.Println("Hello!!!")
}()
}
実行結果
何も出力されません!goroutine内の処理が実行される前にプログラム自体が終了するからです。例えば以下のようにSleepすれば実行されます。package main
import (
"fmt"
"time"
)
func main() {
go func() {
fmt.Println("Hello!!!")
}()
time.Sleep(time.Second)
}
以下のコードの実行結果は何だと思いますか?
package main
import (
"fmt"
"time"
)
func main() {
tmp := 0
for i := 0; i < 100000; i++ {
go func() {
tmp++
}()
}
time.Sleep(time.Second)
fmt.Println(tmp)
}
実行結果
$ ./main
91922
$ ./main
92015
$ ./main
92465
$ ./main
92374
そもそもtime.Sleep
で処理を待つことは正しくありません。いつ処理が終了するか分からないので処理の終了まで待つべきです。
処理を待つ時によく使われるのがchannelです。channelを使うことでgoroutine間で通信することができます。この辺りからつまずく人が出てきます。
package main
import "fmt"
func main() {
c := make(chan int)
go func() {
// 送信
c <- 100
}()
// 受信
i := <-c
fmt.Println(i)
}
出力結果:
100
以下のコードを実行すると
package main
import "fmt"
func main() {
c := make(chan int)
go func() {
// c <- 100
}()
i := <-c
fmt.Println(i)
}
デッドロックになるので異常終了します
fatal error: all goroutines are asleep - deadlock!
以下のコードを実行したらどうなるでしょうか?
for i := 0; i < 10000; i++ {
go func(i int) {
// 何か重い処理をする
}(i)
}
実行結果
重い処理が同時に10000個同時に実行されます。本当に重い処理だった場合マシンが死にます。channelを使ってみます。channelにはcapacityの概念があります。
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan struct{}, 3)
for i := 0; i < 10000; i++ {
c <- struct{}{}
go func(i int) {
defer func() {
<-c
}()
time.Sleep(time.Second)
fmt.Println(i)
}(i)
}
}
他にも色んなパターンがあります。最初は意味不明かもしれませんが、分かってくると楽しくなってきます。