Posted at

【Golang】goroutine・channelの並列処理で個人的によく使う処理

More than 1 year has passed since last update.


はじめに

Goで並列処理を書く時に、例えばgoroutineの数は上限10個で処理が終わったらまたgoroutineを立ち上げて処理をするみたいなものだったりとか個人的によく使う物を紹介します。

※最適な書き方とかではなくて、こう書けばこう動くよ集です。

※sleepはコピペした時にわかりやすいようにと処理を止めを簡略化するために使ってます


goroutineの処理が終わるまで処理を止める


bad.go

func main() {

go func(s string) {
log.Println("Hello",s)
}("World")
}

これだとgoroutineが処理される前にメインが終わり、Hello Worldが表示されない。


WaitGroupを使った処理止め


good.go

func main() {

wg := sync.WaitGroup{}
wg.Add(1)
go func(s string) {
log.Println("Hello",s)
wg.Done()
}("World")
wg.Wait()
}

これで表示される。

関数名を見たらそれっぽい動きをイメージができると思います。


channelを使った処理止め


good.go

func main() {

ch := make(chan string)
go func(s string) {
log.Println("Hello",s)
ch <- "End function"
}("World")
<- ch // log.Println(<- ch)
}

処理を止めるという意味では同じように処理できます。

ch <- "End function"でchというチャンネルにEnd functionという文字列を流し込み

<- chではチャンネルに値が入ってくるまで処理を止めます。

log.Println(<- ch)と書いても同じく処理を止めて、チャンネルに値が流れるとその値を取り出して関数の引数に入れます


goroutineの数を制限する

これは本当によく使います。

goroutine上限を2つとし、Hello Worldと10回出力するプログラムです。

何を言っているか分からない場合はコピペして実行してください。


good.go

func main() {

ch := make(chan int, 2)
wg := sync.WaitGroup{}

for i := 0; i < 10; i++ {
ch <- 1
wg.Add(1)
go func(s string) {
log.Println("Hello", s)
//time.Sleep(time.Second) //コピペして実行する時はスリープ入れると理解しやすい
<- ch
wg.Done()
}("World" + strconv.Itoa(i))
}
wg.Wait()
}


WaitGroup・channelを組み合わせて使います。

WaitGroupでメイン処理を止めて、チャンネルで上限を制御しています。

WaitGroupはメイン処理を止めているだけなのであまり気にしないでください。チャンネルが重要です。

ch := make(chan int, 2)第二引数に2とあり、これが結果的にgoroutineの上限になります。

第二引数の値がチャンネルに流せる上限の値で、それ以上の数をチャンネルに流しても上限が決まっているのでチャンネルが受け取ることができずに、チャンネルに空きができるまで流す段階で処理が止まります。

ロジックとしては、並列処理を始める前にch <- 1と書きチャンネルに適当な値を流し込みます

上限が2つなので、最初の2回のgoroutineは普通に起動しますが、3回目からch <- 1の部分で既に二つの値が入っているのでチャンネルが空くのを待ちます。

go funcの並列処理の最後に<- chと書きチャンネルから値を取り出すと3回目のチャンネルが空くのを待っているところが実行され、4回目が次に空くのを待ちます。結果的に上限二つとして並列処理を実現します。


チャンネルに値が入るのをずっと待つgoroutine

順番に処理しないといけない場合や処理を貯めてgoroutineが増えすぎないように制御できます。

処理を貯められるので直ぐさま処理しなくて良いような内容だと安定的に処理できたりするかもしれませんね。

注意点としてch := make(chan string,5)の第二引数を指定しないとメインが処理しちゃいます。。。(どうなってんだこれ・・・)


good.go

func main() {

ch := make(chan string,5)
go goroutine(ch)

ch <- "World0"
ch <- "World1"
ch <- "World2"
ch <- "World3"
ch <- "World4"
log.Println("Hello World5")
time.Sleep(time.Second*5)
}

func goroutine(ch chan string) {
for s := range ch {
log.Println("Hello",s)
time.Sleep(time.Second)
}
}



上限あり


good.go

func main() {

ch := make(chan string,5)
go goroutine(ch)

ch <- "World0"
ch <- "World1"
ch <- "World2"
ch <- "World3"
ch <- "World4"
log.Println("Hello World5")
time.Sleep(time.Second*5)
}

func goroutine(ch chan string) {
blockCh := make(chan int,2)
for out := range ch {
blockCh <- 1
go func(s string) {
log.Println("Hello",s)
time.Sleep(time.Second)
<-blockCh
}(out)
}
}


個人的には大体これくらいで事足りてる感じですね。

他にもよく使うものがあれば教えてください><