※本記事は備忘録として記述しておりますので、ご容赦ください。
sync.Mutex
次のコードは、IDを作成するコードが同時に1つしか実行されないようにしています。
※goroutineにmutex構造体を渡すときは、必ずポインタでわたすこと。
値のまま渡してしまうと、ロックしている状態のまま別のsync.Mutexインスタンスを渡すことになってしまうので、注意が必要です。
package main
import (
"fmt"
"sync"
"time"
)
var id int
func generateID(mutex *sync.Mutex) int {
// Lock() / Unlock()をペアで呼び出してロックする
mutex.Lock()
defer mutex.Unlock()
id++
result := id
return result
}
func main(){
// sync.Mutex構造体の変数宣言
// 次の宣言をしてもポインタ型になるだけで正常に動作する
// mutex := new(sync.Mutex)
var mutex sync.Mutex
for i := 0; i < 100; i++ {
go func() {
fmt.Printf("id: %d\n", generateID(&mutex))
}()
}
// 100まで出力させたいなら終了待ちの操作が必要
// time.Sleep(4 * time.Second)
}
# 実行結果
id: 1
id: 5
id: 6
id: 2
id: 7
id: 4
id: 3
id: 8
id: 10
id: 11
id: 12
id: 13
id: 14
id: 15
id: 16
id: 17
id: 18
id: 9
id: 19
id: 20
...
省略
# goroutineの終了待ちをしていないので、100個に到達する前に終了してしまうことがある
ロックしたままの値を渡していないか確認するには、go vet
を実行することで見ることができる。
Mutexとチャネルの使い分け
- チャネルが有用な場合
- 非同期で結果を受け取る場合
- Mutexが有用な場合
- キャッシュ、状態管理
sync.WaitGroup
多数のgoroutineで実行しているジョブの終了待ちに使います。
※goroutineにwg構造体を渡すときは、Mutex同様必ずポインタでわたすこと。
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
// ジョブ数をあらかじめ登録 (必ずgoroutineを作成する前に呼ぶ)
// Doneが呼ばれるたびに、数がデクリメントされていく
// Waitはこの中の数が0になるのを待機している
wg.Add(2)
go func() {
// 非同期で仕事をする
fmt.Println("仕事1")
// Doneで完了を通知
wg.Done()
}()
go func() {
// 非同期で仕事をする
fmt.Println("仕事2")
// Doneで完了を通知
wg.Done()
}()
// 全ての処理が終わるのを待つ
wg.Wait()
fmt.Println("完了")
}
# 出力結果 (仕事1と仕事2は順不同で出力される)
仕事2
仕事1
完了
# Mutexと違い、終了待ちをしているため、確実にgoroutineの終了を確認できる
WaitGroupとチャネルの使い分け
- WaitGroupの方が良い場合
- ジョブ数が大量にある場合
- 可変個の場合