A seemingly clever trick to avoid locking in concurrent C code goes like this: I have a global variable ptr
which points to a mystruct
and I want to update that structure. So I'll allocate a new mystruct
, fill the data in and only then I'll make the change visible to the world by pointing ptr
to the new mystruct
object.
This is incorrect as it depends on the ordering of writes and there's no guarantee that the write to ptr
will become visible to other threads after all stores to the new mystruct
have taken place. Therefore, the new mystruct
object can be returned partially initialized.
My question is: can this happen in Go, too? I think it can, but I have to say I found The Go Memory Model a little incomprehensible.
I wrote a bit of Go code to test it out, but on my machine, the bad behavirour does not manifest itself:
package main
import (
"fmt"
"time"
)
type mystruct struct {
a int
b int
}
var (
ptr *mystruct
counter int
)
func writer() {
for {
counter += 1
s := mystruct{a: counter, b: counter}
ptr = &s
}
}
func reader() {
time.Sleep(time.Millisecond)
for {
if ptr.a != ptr.b {
fmt.Println("Oh no, I'm so buggy!")
}
}
}
func main() {
go writer()
go reader()
select {}
}
This of course proves nothing.
Can you please provide a very brief comparison of memory guarantees provided by Go's goroutines with (almost no guarantees) provided by a POSIX thread in C?
Version of May 31, 2014
Advice
If you must read the rest of this document to understand the behavior of your program, you are being too clever.
Don't be clever.
I [David] wrote a bit of Go code to test it out.
Your Go program has data races. The results are undefined.
$ go run -race david.go
==================
WARNING: DATA RACE
Read at 0x000000596cc0 by goroutine 7:
main.reader()
/home/peter/gopath/src/david.go:29 +0x4b
Previous write at 0x000000596cc0 by goroutine 6:
main.writer()
/home/peter/gopath/src/david.go:22 +0xf8
Goroutine 7 (running) created at:
main.main()
/home/peter/gopath/src/david.go:37 +0x5a
Goroutine 6 (running) created at:
main.main()
/home/peter/gopath/src/david.go:36 +0x42
==================
==================
WARNING: DATA RACE
Read at 0x00c0000cc270 by goroutine 7:
main.reader()
/home/peter/gopath/src/david.go:29 +0x5b
Previous write at 0x00c0000cc270 by goroutine 6:
main.writer()
/home/peter/gopath/src/david.go:21 +0xd2
Goroutine 7 (running) created at:
main.main()
/home/peter/gopath/src/david.go:37 +0x5a
Goroutine 6 (running) created at:
main.main()
/home/peter/gopath/src/david.go:36 +0x42
==================
==================
WARNING: DATA RACE
Read at 0x00c0000cda38 by goroutine 7:
main.reader()
/home/peter/gopath/src/david.go:29 +0x7f
Previous write at 0x00c0000cda38 by goroutine 6:
main.writer()
/home/peter/gopath/src/david.go:21 +0xd2
Goroutine 7 (running) created at:
main.main()
/home/peter/gopath/src/david.go:37 +0x5a
Goroutine 6 (running) created at:
main.main()
/home/peter/gopath/src/david.go:36 +0x42
==================
<<SNIP>>
Your Go program: david.go
:
package main
import (
"fmt"
"time"
)
type mystruct struct {
a int
b int
}
var (
ptr *mystruct
counter int
)
func writer() {
for {
counter += 1
s := mystruct{a: counter, b: counter}
ptr = &s
}
}
func reader() {
time.Sleep(time.Millisecond)
for {
if ptr.a != ptr.b {
fmt.Println("Oh no, I'm so buggy!")
}
}
}
func main() {
go writer()
go reader()
select {}
}
Playground: https://play.golang.org/p/XKywmzrRRRw