goroutineの入門用の内容をみてそのまま実装すると、大量のgoroutineを作るようになってしまいます。
goroutine大爆発を起こすとメモリを大量に使い、なおかつGCされないので大変な事になってしまうでしょう。
そんな事にならないように、goroutineを使う際は同時実行数を制御する必要性が出て来ます。
簡単ですが、ちょっとした方法でgoroutineを制御し、メモリの抑制ができるのでやって見ました。
#とりあえず
チャンネルを使って、ブロック制御をしてあげれば良いというのが、よくある話ですので、
ここでもその方法でサンプルコードを書いてみます。
ついでにですが、goroutineの同時実行数を制御する事でメモリがどのようになってるかを、
ログに出すようにしてみました。
package main
import (
"log"
"syscall"
//"runtime"
"sync"
)
func logMeory() {
var rusage syscall.Rusage
syscall.Getrusage(syscall.RUSAGE_SELF, &rusage)
megaBytes := int64(1024 * 1024)
log.Printf("memory = %vMB\n\n", rusage.Maxrss/megaBytes)
}
func createGoroutines(num int) {
// チャンネルは作りたいgoroutineの数分設定
ch := make(chan int, num)
// goroutine同時実行数
limitter := make(chan int, 10)
wg := new(sync.WaitGroup)
defer close(ch)
for i := 0; i < num; i++ {
wg.Add(1)
limitter <- 1
go testGoroutine(ch, wg, limitter)
// goroutineの数見るよう
//log.Println("goroutine:", runtime.NumGoroutine())
}
wg.Wait()
}
func testGoroutine(ch chan int, wg *sync.WaitGroup, limitter chan int) {
defer wg.Done()
defer func() { <-limitter }()
// なんかメモリ使う
a := make([]byte, 1024*1024*100)
for i := range a {
a[i] = byte(i)
}
ch <- 1
}
func main() {
a := make([]byte, 1024*1024*100)
for i := range a {
a[i] = byte(i)
}
log.Println("initialize memory")
logMeory()
createGoroutines(100)
log.Println("goroutine used: 100")
logMeory()
createGoroutines(200)
log.Println("goroutine used: 200")
logMeory()
createGoroutines(300)
log.Println("goroutine used: 300")
logMeory()
createGoroutines(400)
log.Println("goroutine used: 400")
logMeory()
}
ポイントとしては、 main.createGoroutinesの中で、limitterというチャンネルを用意し、10と設定しいるところです。
あとは、main.testGoroutineの中でチャンネルの中身を捨ててあげれば、limitterに空きができるので、
limitterに1を渡してあげてるところで、実行数制御が行えます。
#メモリはどうなるか?
せっかく制御したのですから、メモリ状況を見ないと愉しくないので、
少し見て見ましょう。
##limitter 1の場合
まずは小手調べ
2017/12/25 05:48:12 initialize memory
2017/12/25 05:48:12 memory = 104MB
2017/12/25 05:48:16 goroutine used: 100
2017/12/25 05:48:16 memory = 105MB
2017/12/25 05:48:24 goroutine used: 200
2017/12/25 05:48:24 memory = 208MB
2017/12/25 05:48:35 goroutine used: 300
2017/12/25 05:48:35 memory = 208MB
2017/12/25 05:48:52 goroutine used: 400
2017/12/25 05:48:52 memory = 209MB
200以上を使う場合は、これ以上メモリが増えないのでいい感じですね。
##limitter 10の場合
これは結構増えそうです。
2017/12/25 05:58:59 initialize memory
2017/12/25 05:58:59 memory = 104MB
2017/12/25 05:59:01 goroutine used: 100
2017/12/25 05:59:01 memory = 1859MB
2017/12/25 05:59:04 goroutine used: 200
2017/12/25 05:59:04 memory = 1963MB
2017/12/25 05:59:09 goroutine used: 300
2017/12/25 05:59:09 memory = 2066MB
2017/12/25 05:59:16 goroutine used: 400
2017/12/25 05:59:16 memory = 2066MB
そこそこ増えましたね。
##limit 100の場合
これはどうでしょうか?
2017/12/25 06:03:35 initialize memory
2017/12/25 06:03:35 memory = 104MB
2017/12/25 06:03:41 goroutine used: 100
2017/12/25 06:03:41 memory = 6626MB
2017/12/25 06:04:16 goroutine used: 200
2017/12/25 06:04:16 memory = 9929MB
2017/12/25 06:05:28 goroutine used: 300
2017/12/25 06:05:28 memory = 10110MB
2017/12/25 06:10:12 goroutine used: 400
2017/12/25 06:10:12 memory = 10110MB
いやぁさすがに洒落にならない感じになりましたね。
Goland上で実行しているのと、同時にたくさんmake動かす事で、
Macのメモリ食いつぶして、メモリデータ圧縮するのに時間を取られて遅くなってしまいました。
#まとめ
このようにgoroutineを大量に使えばよくなるという事でもなく、
適切に使うことが大切だということがわかりました。
GCがいい感じにgoroutineもメモリ管理してくれるわけではないので、
goroutineを使う際はメモリの事も気にして制御をかけてあげないとなりませんね。
#追伸
こんな時間まであれこれしてましたが、我が家にはサンタさんは来てくれませんでした。
もっと早くに記事を書けなかった悪い子だからでしょうか……