goroutineの同時実行数を制御するやつ

goroutineは気軽に使えるのでバコバコ使ってしまうけど、同時に実行されすぎると困ることがある。

例えばサイトのスクレイピングとかで同時に3アクセスまでに制限したいとか。

だいたいこんな感じのコードになる。

package main

import (
    "sync"
)

func main() {
    ch := make(chan struct{}, 3)
    wg := &sync.WaitGroup{}
    arr := []string{"https://a.com/", "https://b.com/", "https://c.com/", "https://d.com/"}
    for i := 0; i < len(arr); i++ {
        wg.Add(1)
        go func(j int) {
                         ch <- struct{}{}
            defer wg.Done()
                         // urlにアクセスしてなんかする
                         doScraping(arr[j])
                         <-ch
        }(i)
    }
    wg.Wait()
}

同時に実行されるgoroutineの数を制限するためには同時に処理されたい最大値をcapacityに持つchannelを作るという手法を使う。

慣れるとかんたんなことなんだけど、少しわかりづらいなーと思うことがあったのでこんな感じで制御したいという願望を込めた小さいライブラリを作った。

github.com

上記のコードのchannelの生成とかstructの追加のところを構造体のメソッド化してるだけ。

police.Limit()で同時実行数を指定して、police.Block()からpolice.Release()で囲む。

package main

import (
    "sync"

    "github.com/YuheiNakasaka/police"
)

func main() {
    // initialize
    police := &police.Arrival{}
    // set the count of goroutine processed at the same time
    police.Limit(3)

    wg := &sync.WaitGroup{}
    arr := []string{"https://a.com/", "https://b.com/", "https://c.com/", "https://d.com/"}
    for i := 0; i < len(arr); i++ {
        wg.Add(1)
        go func(j int) {
            police.Block()

            defer wg.Done()
            // urlにアクセスしてなんかする
            doScraping(arr[j])

            police.Release()
        }(i)
    }
    wg.Wait()
}

ぶっちゃけコード量はchannelを直で使うより増えてるんだけど、なんとなくコードは読みやすくなるかなという感じがする。

ちなみに警察官が交通整理してるみたいなイメージでpoliceという名前になってる。

大した処理じゃないけどこういうのもありかなという感じで作った。

ほんとはsync.WaitGroupにLimitみたいなメソッドが生えてればいいのかもと思ったけど、そういうもんでもないんですかね、Goの文化的に。

小ネタでした。