研究でquic-goを使っていて、sync.Mutex
というのが出てきて使い方を調べたのでメモ。
そもそもmutexとは
In computer science, a lock or mutex (from mutual exclusion) is a synchronization primitive: a mechanism that enforces limits on access to a resource when there are many threads of execution. A lock is designed to enforce a mutual exclusion concurrency control policy, and with a variety of possible methods there exists multiple unique implementations for different applications.
(Lock (computer science) - Wikipediaより引用)
コンピュータサイエンスをかじったことがある人なら聞き覚えがあると思いますが、mutexはマルチスレッドプログラミングにおいてあるリソースへのアクセスを制限するロック機構のことです。並列処理においてあるリソースに一度に一つのスレッドしかアクセスできないようにします。
sync.Mutexの使い方
ググったらA Tour of Goが出てきて、これを見たらだいたい使い方がわかりました。
試しに以下のようなコードを書いて挙動を確認しました。
package main
import (
"fmt"
"sync"
"time"
)
func main() {
l := Lock{}
go l.lockWhile(10)
fmt.Println(time.Now().Clock())
l.exclusiveHello()
fmt.Println(time.Now().Clock())
}
type Lock struct {
m sync.Mutex
}
func (l *Lock) exclusiveHello() {
l.lock()
fmt.Println("hello")
l.unlock()
}
func (l *Lock) lock() {
l.m.Lock()
}
func (l *Lock) unlock() {
l.m.Unlock()
}
func (l *Lock) lockWhile(n int) {
l.lock()
time.Sleep(time.Second * time.Duration(n))
l.unlock()
}
実行すると以下のようになります。
❯ go run main.go
16 45 39
hello
16 45 49
l.lockWhile(10)
は非同期に実行しているのでl.exclusiveHello()
がすぐに実行されそうな気もしますが、sync.Mutex
によるロックをかけているのでl.lockWhile(10)
が終わるまでl.exclusiveHello()
の中身が実行されていません。ちゃんと10秒経っていますね。
もちろんl.lockWhile(3)
にすれば3秒ごにhello
が出力されます。
❯ go run main.go
16 50 57
hello
16 51 0
sync.Mutexの中身
sync.Mutex
の中身を見るとこのように書かれています。
// A Mutex is a mutual exclusion lock.
// The zero value for a Mutex is an unlocked mutex.
//
// A Mutex must not be copied after first use.
type Mutex struct {
state int32
sema uint32
}
書いてある通り、値が0ならアンロック状態のmutex(=利用可能)みたいです。
実際にLock()
を見てみると、
// Lock locks m.
// If the lock is already in use, the calling goroutine
// blocks until the mutex is available.
func (m *Mutex) Lock() {
// Fast path: grab unlocked mutex.
if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
if race.Enabled {
race.Acquire(unsafe.Pointer(m))
}
return
}
// Slow path (outlined so that the fast path can be inlined)
m.lockSlow()
}
if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked)
の部分でロック状態の確認とロックを行なっていますね。
もしロックされていたら最後のm.lockSlow()
が呼び出されてロックが解放されるまで待つ処理に移るみたいです。こちらの処理はちょっと複雑になっていましたが、面白そうなので今度読んでみようと思います。