The go memory model is an axiomatic memory model and is defined in terms of a happens-before relation.
So if A happens-before B, then B should see A and everything before A.
In Golang all actions in a single Goroutine create such happens-before edges due to the sequence-before rule. So a Goroutine should always be able to see its own changes.
But if you share state between different Goroutines, things can become problematic if there is no happens-before edge.
2 Memory actions to a shared location are conflicting when at least one of them is a write. When you have 2 conflicting memory actions that are concurrent, so there is no happens-before edge between them, then you have a data race.
And as soon as you have a data race, weird problems can happen. So typically atomicity, visibility, and reordering problems.
In your example, there is a data race on 'a' and 'done'.
func setup() {
a = "hello, world" (1)
done = true (2)
}
func main() {
go setup()
for !done { (3)
}
println(a) (4)
}
The 2 stores in the setup function could be reordered for example. To fix this problem you would need to update 'done' using an atomic.
In that case, you have a happens-before edge between (1) and (2) due to sequenced-before. And also between (3) and (4). And between (2) and (3) there is a happens-before edge due to synchronized-before. Since happens-before is transitive, there is a happens-before edge between (1) and (4). And therefore you get the guarantee, that when you read 'done=true', you will see 'a=hello world'.