![見出し画像](https://assets.st-note.com/production/uploads/images/142132703/rectangle_large_type_2_ad93f8b6b4f7d5a09af051381b0e93ab.png?width=1200)
Golang_ゴルーチン&チャネルとselectとsync.WaitGroup #471
Golangは、シンプルかつ強力な並行処理機能を提供します。並行処理の要であるゴルーチンについて以下の記事で整理しました。
この記事ではこれを更に深掘りし、並行処理を効率的に管理するために重要なselect構文とsync.WaitGroupについて詳しく解説します。
select構文
複数のチャネル操作を待機し、最初に準備ができた操作を実行するためのものです。これにより、非同期のチャネル通信を効率的に管理できます。以下が基本的なselect構文の例です。
select {
case : ch1 <- val
// ch1への送信ができた(ch1を受信する準備ができた)場合の処理
case val := <-ch2:
// ch2からの受信があった場合の処理
default:
// どのケースも準備できていない場合の処理
}
例えば以下のように実装できます。
package main
import "fmt"
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x: // cを送信できたら(main()のforで「<-ch」が呼ばれたら)こちらに入る
x, y = y, x+y
case <-quit: // main()のforを抜けて「quit <- 0」が走ったらこちらに入ってforを抜ける
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c) // チャネルから受信するまでは次のループに進まない
}
quit <- 0
}()
fibonacci(c, quit)
}
select構文では複数のチャネル操作を扱えますが、1つのチャネルの複数の状態を操作することもできます。先ほどのコードを編集し、チャネルquitを使わずにクローズしてみます。
package main
import "fmt"
func fibonacci2(c chan int) {
x, y := 0, 1
for {
select {
case c <- x: // cを送信できたら(main()のforで「<-ch」が呼ばれたら)こちらに入る
x, y = y, x+y
case _, ok := <-c: // チャネルの状態を監視し、閉じられたら(main()でforを抜けてcloseされたら)forを抜ける
if !ok {
fmt.Println("quit")
return
}
}
}
}
func main() {
c2 := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Printf("2だぜ %d\n", <-c2)
}
close(c2)
}()
fibonacci2(c2)
}
これにより実装できる機能の幅が広がりそうですね。
sync.WaitGroup
こちらは複数のゴルーチンの完了を待つための同期プリミティブです。これにより、複数のチャネルが全てクローズされるのを待ってから処理を進める、という実装が可能になります。
以下が基本的な使用方法です。
(1) WaitGroupの宣言:
WaitGroupを宣言します
var wg sync.WaitGroup
(2) カウントの追加:
カウントするゴルーチン数を定義します
wg.Add(n)
(3) ゴルーチンの終了を通知:
ゴルーチン内で遅延実行を定義して終了を通知します
defer wg.Done()
(4) すべてのゴルーチンの完了を待機:
以下のコードで処理を停止し、カウンタが0になるまで待機します
wg.Wait()
具体的なコード例は以下です。
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // 遅延実行でゴルーチンの終了を通知
fmt.Printf("Worker %d starting\n", id)
time.Sleep(time.Second) // 1秒間の作業をシミュレート
fmt.Printf("Worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1) // ゴルーチンの数をカウント
go worker(i, &wg)
}
wg.Wait() // すべてのゴルーチンの終了を待機
fmt.Println("All workers done")
}
goキーワードをつけて関数をゴルーチンで実行する直前でwg.Add(1)でカウンタを1つ増やしています。そして関数が終了するとwg.Done()が遅延実行され、カウンタが1つ減る仕組みです。
ここではwg.Add(1)が3回実行されるので、wg.Done()が3回実行されたタイミングでwg.Wait()が待機を終了し、次の処理に進みます。
これにより複数のゴルーチンを起動して並行性を高めつつ、それぞれの完了を待ってからの処理が可能になります。
ここまでお読みいただきありがとうございました!!
参考
この記事が気に入ったらサポートをしてみませんか?