はじめに
Githubで公開されているソースコードを読みながら、Go言語を勉強しています。
Go言語には並列処理の機能があります。
Githubで公開されているソースコードを読むために、Go言語の並列処理について学ぶ必要がありました。
Go言語には同期処理、排他制御などの機能を提供する便利なsyncパッケージがあります。
今回はこの便利なsyncパッケージのOnce, WaitGroup, Mutexについて書きたいと思います。
この記事ではGo言語のインストール方法や構文などの基本的なことについては触れません。もし、それらに興味があるならば、こちらの記事が参考になると思います。
また、Go言語の並列処理に関する goroutine, channel, select, close などについてはこちらの記事にわかりやすくまとまっています。
sync.Once
まずは、sync.Onceです。関数を一度だけ呼び出したい場合に使用します。
package main
import "fmt"
import "sync"
func main() {
var once sync.Once
once.Do(func() {fmt.Println("A")})
once.Do(func() {fmt.Println("B")}) // こちらの関数は呼び出されない
}
こちらはGo Playgroundへのリンクです。
余談ですが、play goと検索したところ、打ちながら囲碁のルールと基本的なテクニックを学ぶことができるサイトを見つけました。
sync.WaitGroup
sync.WaitGroupについてはこちらの記事にわかりやすくまとまっています。
自分のために少しまとめておきます。
同期待ちにsync.WaitGroupを使用します。
sync.WaitGroupには、定石があるように思います。
それはgoroutineの起動前にAdd(1)、goroutineの終了前にDone()、同期待ちにWait()という定石です。
package main
import "fmt"
import "sync"
func main() {
var wait sync.WaitGroup
wait.Add(1) // goroutineの起動前
go func() {
defer wait.Done() // goroutineの終了前
fmt.Println("A")
}()
wait.Add(1) // goroutineの起動前
go func() {
defer wait.Done() // goroutineの終了前
fmt.Println("B")
}()
wait.Wait() // 同期待ち
}
こちらはGo Playgroundへのリンクです。
sync.Mutex
排他制御にsync.Mutexを使用します。
case1
まずはsync.Mutexを使用しない場合を見てみます。
package main
import "fmt"
import "sync"
func main() {
c := 0
var w sync.WaitGroup
for i := 0; i < 300; i++ {
w.Add(1)
go func() {
defer w.Done()
c++
}()
w.Add(1)
go func() {
defer w.Done()
fmt.Println(c)
}()
}
w.Wait()
}
期待する結果は1から300が出力されることです。
期待しない結果になることを確認します。
こちらはGo Playgroundへのリンクです。
同じ数字が出力されていること、最後が300でないことから期待しない結果だとわかります。
case2
なにが起こったのか確認します。
package main
import "fmt"
import "sync"
func main() {
c := 0
var w sync.WaitGroup
var m sync.Mutex
for i := 0; i < 300; i++ {
w.Add(1)
go func() {
defer w.Done()
m.Lock()
c++
}()
w.Add(1)
go func() {
defer w.Done()
m.Unlock()
fmt.Println(c)
}()
}
w.Wait()
}
こちらはGo Playgroundへのリンクです。
実行中にパニックが生じます。
panic: sync: unlock of unlocked mutex
前者のgoroutineでロックして、後者のgoroutineでアンロックしています。
後者のgoroutineが前者のgoroutineを追い越し、アンロックしたミューテックスを、ロックする前に、再度アンロックしたようですね。
case3
つぎにsync.Mutexを使用する場合を見てみます。
package main
import "fmt"
import "sync"
func main() {
c := 0
var w sync.WaitGroup
var m sync.Mutex
for i := 0; i < 300; i++ {
w.Add(1)
m.Lock()
go func() {
defer w.Done()
defer m.Unlock()
c++
}()
w.Add(1)
m.Lock() // ロック中ならば、アンロックを待つ
go func() {
defer w.Done()
defer m.Unlock()
fmt.Println(c)
}()
}
w.Wait()
}
こちらはGo Playgroundへのリンクです。
期待する結果です。
実装でこちらを参照しました。
私の扱った例が悪かったかもしれません。
期待する結果ですが、すでに、並列処理になっていませんね。。。
まとめ
syncパッケージについてはこちらの記事にわかりやすくまとまっています。
いろいろと試した結果をわかりやすくまとめたつもりです。
sync.Mutexで扱った例は少し適切ではなかったかもしれませんが、参考になれば幸いです。