よくハマる goroutine の落とし穴
- goroutine は便利だが落とし穴もある
- 頻発する落とし穴の例を紹介
罠の例 (1) goroutine と for ループ
- goroutine を使って配列の中身をダンプしてみることにした
- 以下を実行するとどうなるでしょうか (playground)
package main
import (
"fmt"
"time"
)
func main() {
ints := []int{1, 2, 3, 4, 5, 6}
for _, n := range ints {
go func() {
fmt.Println(n)
}()
}
time.Sleep(3 * time.Second)
}
- なんか 6 ばっかり出たんですけど
- goroutine に処理が切り替わるのは、for ループが終わった後
- for ループが終わった段階で n を参照すると、ループの最後の数字が入っている
- goroutine の処理はいつ始まるか分からないし、何なら順序の保証もないと考えておく
修正案: goroutine に引数を渡す
package main
import (
"fmt"
"time"
)
func main() {
ints := []int{1, 2, 3, 4, 5, 6}
for _, n := range ints {
go func(i int) {
fmt.Println(i)
}(n)
}
time.Sleep(3 * time.Second)
}
罠の例 (2) WaitGroup してるのに?
package main
import (
"fmt"
"sync"
)
func main() {
wg := sync.WaitGroup{}
go func(s string) {
wg.Add(1)
fmt.Println(s)
wg.Done()
}("hello world!")
wg.Wait()
}
- 何も表示されない…?
- 割とやりがち
- goroutine の中で
wg.Add(1)
するのは誤り
- 結局 goroutine が始まる前にこのプログラムは終わる
修正案: goroutine の外で wg.Add(1)
する
- goroutine の手前で
wg.Add(1)
する
package main
import (
"fmt"
"sync"
)
func main() {
wg := sync.WaitGroup{}
wg.Add(1)
go func(s string) {
fmt.Println(s)
wg.Done()
}("hello world!")
wg.Wait()
}