[Go] Go言語による並行処理 3章メモ1

Go言語による並行処理の第3章をまとめていく。 www.amazon.co.jp

3.1 ゴルーチン

関数呼び出しの前にgoキーワードを置くことでゴルーチンを起動できる。

func main() {
    go f()
    ...
}

func f {
    ...
}

3.2 syncパッケージ

3.2.1 WaitGroup

Addでカウンターを1増やし、Doneで1減らす。Waitを呼び出すとカウンターがゼロになるまでブロックする。

var wg sync.WaitGroup

wg.Add(1)
go func() {
    defer wg.Done()
    ...
}

wg.Wait()

3.2.2 MutexとRWMutex

プログラム内のクリティカルセクションを保護する。 以下の例の場合、変数aを保護、その解除を行っている。

var a int
var lock sync.Mutex

... 
go func() {
    lock.Lock()
    defer lock.Unlock()
    a++
}
...

RWMutexは読み込みと書き込みを区別することができる。Lockは上のMutexでのLockと同様、読み書き両方をロックする。RLockは書き込みのみをロックし、読み込みは可能となる。

var lock sync.RWMutex

lock.RLock()
lock.RUnlock()

lock.Lock()
lock.Unlock()

3.3 Cond

イベントの待機や発生を知らせる。

c := sync.NewCond(&sync.Mutex{})

func f() {
    c.L.Lock()
    // クリティカルセクションを操作する処理
    c.L.Unlock()
    c.Signal()
}

func main() {
    c.L.Lock()
    for conditionTrue() == false {
        c.Wait()
    }
    // クリティカルセクションを操作する処理
    go f()
    c.L.Unlock()
}

sync - Go 言語において、Waitは

自動的に c.L のロックを解除し,呼び出し側のゴルーチンの実行を中断します。 後で実行を再開した後, Wait は c.L をロックしてから戻ります。

これを上の例で見ると以下のようになる。

  1. conditionTrue()がfalseになったら、ループに入り、c.Wait()を呼び出すことで呼び出し側(main関数)の処理を一旦停止し、c.Lのロックを解除する。
  2. 関数fでクリティカルセクションを操作する処理が行われ、c.Signal()を実行することで、c.Wait()で停止されていたmain関数の処理が再開される。その際、main関数でc.Lが再びロックされる。
  3. conditionTrue()がfalseのままであったら、1に戻り、trueであったらループから抜け出し、その後の処理を行う。

c.Broadcast()ではc.Wait()となっているすべてのゴルーチンにシグナルを伝える。

3.2.4 Once

sync.Once.Doは一度しか呼び出されない。

Pool

使うものを決まった分だけ作る方法。 Getメソッドではプールに必要なインスタンスがあるか確認し、あれば呼び出し元にそれを返す。なければ、Newを呼び出した結果を返す。作業が終わるとPutメソッドを用いて使っていたインスタンスをプールに返す。