🐈

Go言語、WaitGroupとMutexの使い方

2023/06/07に公開

Goの並行性を管理するための機能として、WaitGroupとMutexがあります。
簡単な使い方を復習していきます。

WaitGroup使ってみた

これはgoroutineの終了を全て待つための機能です。
goroutineは非同期で実行されるため、main関数が終了してもgoroutineが終了していない場合があります。
つまり、複数のgoroutineが全て終わらずにmain関数が終了してしまうということです。

Syncパッケージとは

Go言語の標準パッケージの一つで、並行処理を行うための機能が提供されています。
基本的な機能が詰まっているので見かけることが多いかも。

package main

import (
	"fmt"
	"sync" // WaitGroupを使うために必要
	"time"
)

func main() {
	var wg sync.WaitGroup // WaitGroupの宣言

	for i := 1; i <= 5; i++ {
		wg.Add(1) // 待つgoroutineの数分増やす必要がある
		go worker(i, &wg)
	}

	wg.Wait() // goroutineが終了するまで待つ
	fmt.Println("All workers done")
}


func worker(id int, wg *sync.WaitGroup) {
	defer wg.Done() // goroutineが終了したことを通知

	fmt.Printf("Worker %d starting\n", id)

	time.Sleep(time.Second)
	fmt.Printf("Worker %d done\n", id)
}
  1. WaitGroupの宣言
  2. goroutineを実行する前に、待つgoroutineの数分Addする
  3. goroutineの中でDoneを呼び出す
  4. main関数でWaitを呼び出す
    という感じです!

WaitGroupに追加した分だけDoneを呼び出されると、Waitが解除されます。

deferとは

deferは関数の最後に実行される処理を指定することができます。
Taskのような物を作らないでよいので明確に最後に実行するとわかっていればどんどん使っていきたいですね!

Mutex使ってみた

これは複数のgoroutineで1つの値にアクセスするときに、同時にアクセスしないようにするための機能です。
同時にアクセスしてしますと、値が壊れてしまう可能性がありエラーを吐くことがあります!!

package main

import (
	"fmt"
	"sync"
)

func main() {
	c := &Counter{} // Counterの初期化

	var wg sync.WaitGroup // WaitGroupの宣言

	for i := 0; i < 1000; i++ {
		wg.Add(1) // 待つgoroutineの数分増やす必要がある
		go func() { // goroutineの実行
			defer wg.Done() // goroutineが終了したことを通知
			c.Increment() // Counterの値を増やす
		}()
	}

	wg.Wait() // goroutineが終了するまで待つ
	fmt.Println(c.val) // 1000
}

type Counter struct {
	val int // 値
	mux sync.Mutex // Mutexの宣言
}

func (c *Counter) Increment() {
	c.mux.Lock() // Lockをかける
	c.val++ // 値を増やす
	c.mux.Unlock() // Lockを解除する
}
  1. 値のインスタンスを作成
  2. goroutineを実行する前に、待つgoroutineの数分Addする
  3. goroutineの中で終了の通知予約
  4. goroutineの中で値を増やす関数を呼ぶ
  5. main関数でWaitを解除
    という感じです!

Increment関数の中身について

Counterの値を増やす関数になります。
valという値には、複数のgoroutineからアクセスされる可能性があるため、Mutexを使ってLockをかけています。
Lockをかけることで、他のgoroutineからのアクセスをブロックすることができます。
値を増やしてから、Unlockを呼び出すことで、他のgoroutineからのアクセスを許可します。

例えでいうと、valという井戸があります。
そこでは、複数の人が水を汲みに来ることができます。
しかし、同時に汲みに来ると、井戸が壊れてしまう可能性があります。
そこで、井戸の周りには鍵がかかっていて、1人ずつしか入れないようになっています。
1人が入って、水を汲みに行き、出てきたら鍵をかけて、次の人が入るという感じです。

まとめ

goroutineにもやはり不完全な部分があるのでしっかりとその特性を覚えて設計や実装時に意識しておくことが大事だと感じました!
次回はエラーハンドリングについて書いていきます!では

Discussion