LoginSignup
0
0

More than 5 years have passed since last update.

goroutineあれこれ3

Last updated at Posted at 2018-10-27

お題

前回の続き。

関連記事Index

開発環境

# OS

$ cat /etc/os-release 
NAME="Ubuntu"
VERSION="17.10 (Artful Aardvark)"

# 言語

$ go version
go version go1.10 linux/amd64

実践

■一度に起動するgoroutine数を制御しながら大量のワークロードを捌く

大量といいつつコード例では25ループ回すだけだけど。
実際には、例えばメッセージキューをポーリングしつつ、複数メッセージを受信するたびに捌いていくのとかに使える。
(大抵はフレームワークでやってしまうだろうけど)HTTPリクエストを捌くのにも使えると思う。

func main() {
    wg := &sync.WaitGroup{}

    ch := make(chan struct{}, 5)

    for i := 0; i < 25; i++ {
        wg.Add(1)

        ch <- struct{}{} // 5個までは詰められる(でも、それ以上はチャネルが空くまで詰められずにここで待機状態となる)
        go sub(i, ch, wg)
    }

    wg.Wait()
}

func sub(i int, ch chan struct{}, wg *sync.WaitGroup) {
    defer func() {
        <-ch // 処理後にチャネルから値を抜き出さないと、次の goroutine が起動できない

        wg.Done()
    }()

    fmt.Println(i)
    time.Sleep(2 * time.Second)
}

$ go run main.go 
4
1
2
3
0   <- この後、2秒スリープ
6
8
7
5
9   <- この後、2秒スリープ
10
11
12
14
13  <- この後、2秒スリープ
15
16
17
18
19  <- この後、2秒スリープ
20
21
23
22
24  <- この後、2秒スリープ

■時間が来るまで作業を続ける系

実際の業務では、こんな使い方はしないかな。
go1.7から標準パッケージになったcontextを使ってタイムアウトを設定。
メインで起動し、同じくメインの終了をcontextのタイムアウト発動待ちとする。
サブのgoroutineでは時間が来るまで「a」を吐き続ける。

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    go sub(ctx)

    select {
    case <-ctx.Done():
        fmt.Println("END")
    }
}

func sub(ctx context.Context) {
    for {
        fmt.Printf("a ")
        time.Sleep(1 * time.Second)
    }
}
$ go run main.go 
a a a a a a a a a a END

■3重くらいの階層構造を辿ってgoroutine起動

例えば指定のディレクトリ階層配下のファイルそれぞれについて何かしらの処理を行いたい時、
その1つ1つの処理が他の処理と関連なく、平行に行っていい場合には階層をたどりながら全ての処理でgoroutine実行させれば、処理時間の短縮につながる。
ただし、今回の例では含めていないけど、実際には無制限にgoroutine起動させると、処理するファイルが100個あったら100goroutine、1000個あったら1000goroutineを同時に起動させることになるので、
チャネルを使ったgoroutineの実行数制御とも絡めて使うべき。

func main() {
    wg := &sync.WaitGroup{}

    components := createComponents()

    for _, cmp := range components {
        wg.Add(1)
        go cmp.exec(wg)
    }

    wg.Wait()
}

↑メイン関数でのgoroutine起動部分は何度も書いた通り。
今回の肝は、↓の「createComponents()」の内容と、コンポジットパターンの構造の中で composite が実装する exec() の中身。

func createComponents() []component {
    // けっこうエグい構造を作っておく
    return []component{
        newComposite("A", []component{
            newComposite("A-a", []component{
                newLeaf("A-a-1"),
            }),
            newComposite("A-b", []component{
                newLeaf("A-b-1"),
                newLeaf("A-b-2"),
            }),
        }),
        newComposite("B", []component{
            newLeaf("B-a"),
            newComposite("B-b", []component{
                newComposite("B-b-1", []component{
                    newLeaf("B-b-1-1"),
                    newLeaf("B-b-1-2"),
                    newComposite("B-b-1-3", []component{
                        newLeaf("B-b-1-3-1"),
                    }),
                }),
            }),
            newLeaf("B-c"),
        }),
        newLeaf("C"),
        newComposite("D", []component{
            newComposite("D-d", []component{
                newLeaf("D-d-1"),
                newLeaf("D-d-2"),
                newLeaf("D-d-3"),
                newLeaf("D-d-4"),
            }),
        }),
    }
}

コンポジット構造を定義。

type component interface {
    exec(wg *sync.WaitGroup)
}

type composite struct {
    name   string
    childs []component
}

func (c *composite) exec(wg *sync.WaitGroup) {
    defer wg.Done()
    time.Sleep(1 * time.Second)
    fmt.Printf("[COMPOSITE]%s\n", c.name)

    wg2 := &sync.WaitGroup{}
    for _, child := range c.childs {
        wg2.Add(1)
        go child.exec(wg2)
    }
    wg2.Wait()
}

type leaf struct {
    name string
}

func (l *leaf) exec(wg *sync.WaitGroup) {
    defer wg.Done()
    time.Sleep(1 * time.Second)
    fmt.Printf("[LEAF]%s\n", l.name)
}

実行すると、こうなる。

$ go run main.go 
[COMPOSITE]D
[LEAF]C
[COMPOSITE]A
[COMPOSITE]B
[LEAF]B-a
[COMPOSITE]B-b
[COMPOSITE]A-b
[LEAF]B-c
[COMPOSITE]A-a
[COMPOSITE]D-d
[LEAF]D-d-2
[LEAF]D-d-3
[LEAF]A-b-2
[LEAF]D-d-1
[COMPOSITE]B-b-1
[LEAF]D-d-4
[LEAF]A-b-1
[LEAF]A-a-1
[LEAF]B-b-1-2
[LEAF]B-b-1-1
[COMPOSITE]B-b-1-3
[LEAF]B-b-1-3-1

まとめ

あまりまとまりなく、思いつくままにgoroutine使う例を書いていったけど、ひとまずこのへんにしておこう。

0
0
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
0
0