gorutineで並行実行するけど、リソースの問題で大量に並行処理が走らないようにしたいことがあります。
semaphoreを使用することで簡単に、並行実行数を制限させてgorutineを実行させることができます。
どのように使用していくかについて説明していきます。
サンプルコード
インストール
今回のメインとなるsemaphoreをインストール
go get -u "golang.org/x/sync/semaphore"
ソースコード
1~6の数字の配列型を定義して、gorutineでprintNumを実行する。
制限が入っているかわかるようにprintNumの中で5秒待機させている。
semaphore.Weightedのポインタ型の変数sを定義する。s.Acquire(ctx, 1)でリソースが利用されるまでブロックして、利用できればリソースを確保する。s.Release(1)でリソース解放している。
全てのgorutineが実行完了するまで処理を終了させないように、sync.WaitGroupを使用している。
package main
import (
"context"
"fmt"
"log"
"sync"
"time"
"golang.org/x/sync/semaphore"
)
func main() {
doTask()
}
const concurrency = 2 // 最大同時並列実行数
func doTask() {
ctx := context.TODO()
var s *semaphore.Weighted = semaphore.NewWeighted(concurrency)
numbers := []int{1, 2, 3, 4, 5, 6}
var wg sync.WaitGroup
for _, num := range numbers {
wg.Add(1)
go func(n int) {
defer wg.Done()
// リソースの確保と、リソースが利用できるまでここでブロックされている
if err := s.Acquire(ctx, 1); err != nil {
fmt.Println(err)
return
}
// リソースの解放
defer s.Release(1)
printNum(n)
}(num)
}
// 全てのgorutineの実行が終わるまで待つ
wg.Wait()
}
func printNum(n int) {
log.Println("exec", n)
// 処理をわかりやすくするため
second := 5 * time.Second
time.Sleep(second)
log.Printf("executed num: %d \n", n)
}
実行
同時実行数が2つになっていることを確認する。
1, 3を引数にした2つのprintNumの関数が実行されて、処理が完了するまで他のprintNum関数が実行されてない。1, 3が完了された後に、6, 5を引数にしたprintNumの処理が実行されて、6, 5終了後に2, 4を引数にしたprintNumの処理が実行されていて、同時実行数が2つになっている。
semaphore % go run main.go
2023/06/18 17:55:10 exec 1
2023/06/18 17:55:10 exec 3
2023/06/18 17:55:15 executed num: 3
2023/06/18 17:55:15 executed num: 1
2023/06/18 17:55:15 exec 6
2023/06/18 17:55:15 exec 5
2023/06/18 17:55:20 executed num: 5
2023/06/18 17:55:20 exec 2
2023/06/18 17:55:20 executed num: 6
2023/06/18 17:55:20 exec 4
2023/06/18 17:55:25 executed num: 4
2023/06/18 17:55:25 executed num: 2
まとめ
今回は同時実行数を制限して、並行処理を実行できる方法について紹介をしてきました。
他にもGoの並行処理を使うのに必須なchannelやslectの仕組み、Goのロードマップなどについてまとめた記事があるので、そちらも読んでもらえたらと思います。
【おすすめ記事のリンク】
コメント