了解Go的内存模型

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?

The Go Memory Model

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.


Introducing the Go Race Detector


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