gorutineで並行実行数を制限する場合、チャネルを使ったほうがいいですよ

Go言語

以前gorutineで並行実行しすぎると、リソースの問題が発生するので並行実行数を制限したい時は、sempahoreを使おうという記事を書きました。しかしチャネルを使う方がよかったので、今回それを紹介していきます。

以前の記事

試したこと

試しに変数concurrencyの数だけ並行実行をするように、semaphoreとチャネルを使う方法で比較を行う。
gorutineの中身はミリ秒スリープをするだけの処理をする。

package benchmarccorrencuncy

import (
	"context"
	"log"
	"sync"
	"testing"
	"time"

	"golang.org/x/sync/semaphore"
)

const concurrency = 2

func BenchmarkSemaphoreConcurrency(b *testing.B) {
	semaphore := semaphore.NewWeighted(concurrency)
	var wg sync.WaitGroup
	testNum := b.N
	wg.Add(testNum)
	ctx := context.Background()

	for i := 0; i < testNum; i++ {
		go func(num int) {
			defer wg.Done()
			if err := semaphore.Acquire(ctx, 1); err != nil {
				log.Println(err)
			}
			test()
			semaphore.Release(1)
		}(i)
	}
	wg.Wait()
}

func BenchmarkChanConcurrency(b *testing.B) {
	// ただチャネルを使ってgoroutineを制御するだけなので、空の構造体を使う
	ch := make(chan struct{}, concurrency)
	var wg sync.WaitGroup
	testNum := b.N
	wg.Add(testNum)

	for i := 0; i < testNum; i++ {
		go func(num int) {
			defer wg.Done()
			ch <- struct{}{}
			test()
			<-ch
		}(i)
	}
	wg.Wait()
}

func test() {
	time.Sleep(time.Microsecond)
}

動作結果

// concurrency = 2
sanadayouhei@sanadayouheinoMacBook-Pro benchmarc_correncuncy % go test -bench . -benchmem
goos: darwin
goarch: amd64
pkg: test/benchmarc_correncuncy
cpu: Intel(R) Core(TM) i5-1038NG7 CPU @ 2.00GHz
BenchmarkSemaphoreConcurrency-8           222493              4673 ns/op             765 B/op          7 allocs/op
BenchmarkChanConcurrency-8                267619              4255 ns/op             306 B/op          4 allocs/op
PASS
ok      test/benchmarc_correncuncy      2.656s

// concurrency = 3
sanadayouhei@sanadayouheinoMacBook-Pro benchmarc_correncuncy % go test -bench . -benchmem
goos: darwin
goarch: amd64
pkg: test/benchmarc_correncuncy
cpu: Intel(R) Core(TM) i5-1038NG7 CPU @ 2.00GHz
BenchmarkSemaphoreConcurrency-8           345674              3767 ns/op             697 B/op          7 allocs/op
BenchmarkChanConcurrency-8                452593              2585 ns/op             301 B/op          3 allocs/op
PASS
ok      test/benchmarc_correncuncy      3.030s

concurrencyを2, 3で試した結果、チャネルを使った関数(BenchmarkChanConcurrency)がパフォーマンスが良かった。
2→3に増やすことで、パフォーマンスの向上もチャネルを使った関数の方が良かった。

なんでチャネルの方が良いか?

semaphoreのコードを見ると、MutexのLockやUnlockと排他制御をしてWeightedの中身を更新したり、結局チャネルを使って待機している。チャネルだと設定した値以上の受信をしたら、チャネルが送信されるまでブロックされるためだいぶシンプルになっているから、チャネルが早いと考えられる。

sempahoreのコード

sync/semaphore/semaphore.go at master · golang/sync
concurrency primitives. Contribute to golang/sync development by creating an account on GitHub.

まとめ

gorutineの同時実行数を制限したいというだけなら、semaphoreではなくチャネルを使うのが良いことを紹介してきました。
並行処理するにあたって必要なcontextの説明や、なぜチャネルで空の構造体を使うのが良いかの記事を書いています。興味がありましたら読んでもらえたらと思います。

【おすすめ記事のリンク】

コメント

タイトルとURLをコピーしました