LoginSignup
8
1

More than 5 years have passed since last update.

goroutineを使う際のちょっとした方法

Posted at

goroutineの入門用の内容をみてそのまま実装すると、大量のgoroutineを作るようになってしまいます。
goroutine大爆発を起こすとメモリを大量に使い、なおかつGCされないので大変な事になってしまうでしょう。
そんな事にならないように、goroutineを使う際は同時実行数を制御する必要性が出て来ます。
簡単ですが、ちょっとした方法でgoroutineを制御し、メモリの抑制ができるのでやって見ました。

とりあえず

チャンネルを使って、ブロック制御をしてあげれば良いというのが、よくある話ですので、
ここでもその方法でサンプルコードを書いてみます。
ついでにですが、goroutineの同時実行数を制御する事でメモリがどのようになってるかを、
ログに出すようにしてみました。

main.go
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を使う際はメモリの事も気にして制御をかけてあげないとなりませんね。

追伸

こんな時間まであれこれしてましたが、我が家にはサンタさんは来てくれませんでした。
もっと早くに記事を書けなかった悪い子だからでしょうか……

8
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
1