不正な同期
レースを含むプログラムは不正であり、逐次一貫性のない実行を示すことがあります。特に、読み取り r は r と並行して実行される任意の書き込み w によって書き込まれた値を観測することがあることに注意してください。これが発生しても、r の後に発生する読み取りが w の前に発生した書き込みを観測することを意味するものではありません。
このプログラムでは:
var a, b int
func f() {
a = 1
b = 2
}
func g() {
print(b)
print(a)
}
func main() {
go f()
g()
}
g
が 2
を出力してから 0
を出力することがあり得ます。
この事実は、いくつかの一般的なイディオムを無効にします。
ダブルチェックロックは、同期のオーバーヘッドを回避しようとする試みです。例えば、twoprint
プログラムは次のように不正に書かれることがあります:
var a string
var done bool
func setup() {
a = "hello, world"
done = true
}
func doprint() {
if !done {
once.Do(setup)
}
print(a)
}
func twoprint() {
go doprint()
go doprint()
}
しかし、doprint
で done
への書き込みの観測が a
への書き込みの観測を意味するという保証はありません。このバージョンは、"hello, world"
の代わりに(不正に)空文字列を出力することがあります。
もう1つの不正なイディオムは、次のような値のビジーウェイトです:
var a string
var done bool
func setup() {
a = "hello, world"
done = true
}
func main() {
go setup()
for !done {
}
print(a)
}
前と同様に、main
で done
への書き込みの観測が a
への書き込みの観測を意味するという保証はないため、このプログラムも空文字列を出力することがあります。さらに悪いことに、2つのスレッド間に同期イベントがないため、done
への書き込みが main
によって観測されるという保証はありません。main
のループは終了することが保証されません。
この主題には、このプログラムのような、より微妙な変種があります:
type T struct {
msg string
}
var g *T
func setup() {
t := new(T)
t.msg = "hello, world"
g = t
}
func main() {
go setup()
for g == nil {
}
print(g.msg)
}
main
が g != nil
を観測してループを終了しても、g.msg
の初期化された値を観測するという保証はありません。
これらすべての例で、解決策は同じです:明示的な同期を使用することです。