Go言語学習その35~Goroutineその6:排他制御
前回 ( https://note.com/yasmizohawks/n/ncaab0a36736c ) に引き続き
Go における並行処理を実現する Goroutine を見てみます。
■ 排他制御
複数の関数がそれぞれ別の Goroutine 上で動くとき、
各関数が同じ変数を更新するといった場合は排他制御が必要になります。
以下に排他制御無しの場合のサンプルを示します。
package main
import (
"fmt"
"time"
"sync"
)
var sampleStr string
func funcA(wg *sync.WaitGroup) {
defer wg.Done()
sampleStr = "★funcAから設定した文字列"
time.Sleep(10 * time.Millisecond)
fmt.Println("■ funcA:sampleStr=", sampleStr)
}
func funcB(wg *sync.WaitGroup) {
defer wg.Done()
sampleStr = "★funcBから設定した文字列"
time.Sleep(10 * time.Millisecond)
fmt.Println("■ funcB:sampleStr=", sampleStr)
}
func main() {
var wg sync.WaitGroup
wg.Add(2)
go funcA(&wg)
go funcB(&wg)
wg.Wait()
fmt.Println("■ main:終了します")
}
サンプルの内容は、関数の外側に定義されている文字列型変数
に対して、funcA 関数、funcB 関数それぞれで値を設定しています。
実行結果は、実行する度に変わりますが以下のようになります。
実行結果の1行目を見るとfuncB 関数から以下が出力されてますが
funcB 関数の処理を見ると
sampleStr に「★funcBから設定した文字列」を入れてるので
と出て欲しいですよね。
ですが、「time.Sleep(10 * time.Millisecond)」で少し待機してるときに
funcA 関数が動いて sampleStr の内容を
「★funcAから設定した文字列」に変えてしまったために
と出力されたわけです。
排他制御をしていないので、このように別の Goroutine 上の関数の処理が
別の関数の処理に影響を与えてしまいます。
では、上記サンプルに排他制御を入れたものを以下に示します。
package main
import (
"fmt"
"time"
"sync"
)
var sampleStr string
func funcA(wg *sync.WaitGroup, mu *sync.Mutex) {
defer wg.Done()
mu.Lock()
sampleStr = "★funcAから設定した文字列"
time.Sleep(10 * time.Millisecond)
fmt.Println("■ funcA:sampleStr=", sampleStr)
mu.Unlock()
}
func funcB(wg *sync.WaitGroup, mu *sync.Mutex) {
defer wg.Done()
mu.Lock()
sampleStr = "★funcBから設定した文字列"
time.Sleep(10 * time.Millisecond)
fmt.Println("■ funcB:sampleStr=", sampleStr)
mu.Unlock()
}
func main() {
var wg sync.WaitGroup
var mu sync.Mutex
wg.Add(2)
go funcA(&wg, &mu)
go funcB(&wg, &mu)
wg.Wait()
fmt.Println("■ main:終了します")
}
main 関数で以下の変数宣言をしています。
Mutex は排他制御を行うための構造体で、
以下のメソッドが定義されてます。
Lock・・・排他制御したい処理の開始時に実行するメソッド
Unlock・・・排他制御したい処理の終了時に実行するメソッド
Mutex 型の変数 mu を funcA 、funcB 関数の引数に渡して実行していて
funcA 、funcB 関数では以下の処理を記述してます。
funcA 関数で mu.Lock()を実行するとき、
funcB 関数が先に mu.Lock()を実行済みなら
funcA 関数の mu.Lock() は「ロック解除待ち」になり、
mu.Lock() の次行以降の処理は実行されません。
そして、funcB 関数で mu.Unlock() が実行されたら
funcA 関数の mu.Lock() の次行以降の処理が開始されます。
排他制御の入ったサンプルの実行結果は以下になります。
排他制御無しのサンプルでは
となっていた部分が
となってますね。
sampleStr 変数への値の設定と
「■ funcB:sampleStr=~」の出力を
mu.Lock()~mu.Unlock()の間で行っているために
funcB での上記処理中に
funcA 関数で sampleStr 変数に値が設定されてしまうことが
防げたわけですね。