お題
前回の続き。
関連記事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
使う例を書いていったけど、ひとまずこのへんにしておこう。