I am trying to understand the example with incorrect sync code from The Go Memory Model.
Double-checked locking is an attempt to avoid the overhead of synchronization. For example, the twoprint program might be incorrectly written as:
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()
}
but there is no guarantee that, in
doprint, observing the write to done implies observing the write toa. This version can (incorrectly) print an empty string instead of"hello, world".
What are the detailed reasons for an empty string printed in place of "hello world"? I ran this code about five times, and every time, it printed "hello world".
Would the compiler swap a line a = "hello, world" and done = true for optimization? Only in this case, I can understand why an empty string would be printed.
Thanks a lot! At the bottom, I've attached the changed code for the test.
package main
import(
"fmt"
"sync"
)
var a string
var done bool
var on sync.Once
func setup() {
a = "hello, world"
done = true
}
func doprint() {
if !done {
on.Do(setup)
}
fmt.Println(a)
}
func main() {
go doprint()
go doprint()
select{}
}
The reference page about the Go Memory Model tells you the following:
The compiler may therefore reorder the two writes inside the body of the
setupfunction, fromto
The following situation may then occur:
doprintgoroutine doesn't observe the write todoneand therefore initiates a single execution of thesetupfunction;doPrintgoroutine observes the write todonebut finishes executing before observing the write toa; it therefore prints the zero value ofa's type, i.e. the empty string.You need to understand the distinction between a synchronization bug (a property of the code) and a race condition (a property of a particular execution); this post by Valentin Deleplace does a great job at elucidating that distinction. In short, a synchronization bug may or may not give rise to a race condition; however, just because a race condition doesn't manifest itself in a number of executions of your program doesn't mean your program is bug-free.
Here, you can "force" the race condition to occur simply by reordering the two writes in
setupand adding a tiny sleep between the two.(Playground)
This may be enough to convince you that the program indeed contains a synchronization bug.