gogoroutinememory-model

Why is a data race being reported even if assignment is atomic?


Why is following code:

package main

import (
    "fmt"
    "time"
)

func main() {
    str := "ab"
    strPtr := &str
    go func() {
        str2 := "cd"
        strPtr = &str2
    }()
    time.Sleep(2 * time.Second)
    fmt.Printf("current address %p\n", strPtr)
}

producing a data race when run with go run -race main.go? Datarace looks like this:

==================
WARNING: DATA RACE
Read at 0x00c0000b4018 by main goroutine:
  main.main()
      /Users/***/main/main.go:16 +0x124

Previous write at 0x00c0000b4018 by goroutine 7:
  main.main.func1()
      /Users/***/main/main.go:13 +0x7c

Goroutine 7 (finished) created at:
  main.main()
      /Users/***/main/main.go:11 +0x110
==================
current address 0xc000180000
Found 1 data race(s)
exit status 66

Datarace is reported for read from address 0x00c0000b4018 on line fmt.Printf("current address %p\n", strPtr), but at that time the strPtr already points to different address 0xc000180000! How comes?

And even if, by some miracle, there would be a datarace, what I am doing here is assigning a pointer, which based on go memory model should be atomic, re-quoting via this SO answer:

As of today, the version on June 6, 2022 of the Go memory model guarantees that a memory access not larger than a machine word is atomic.

Otherwise, a read r of a memory location x that is not larger than a machine word must observe some write w such that r does not happen before w and there is no write w' such that w happens before w' and w' happens before r. That is, each read must observe a value written by a preceding or concurrent write.


Solution

  • It is a datarace because you have a concurrent memory read/write operation without explicit synchronization. The pointer assignment in the goroutine is concurrent with the pointer read in the main goroutine.

    The fact that a memory write for a pointer-size value is atomic is not relevant to a memory race. There is a race simply because the read operation can see the value of the pointer before the assignment, or the value after it, because there is no happened-before relationship between the memory-read and memory-write. So it is unlikely, but theoretically possible to observe two values for that pointer.