0

I found a data race during testing with -race flag. The data race happened on when updating a struct and read value from the struct method. Later I found to change the method from non-pointer receiver to a pointer receiver can solve the data race. But I don't understand the reason. Can anyone explain the reason?

package main

import (
    "fmt"
    "testing"
)

type TestStruct struct {
    display    bool
    OtherValue int
}

func (t TestStruct) Display() bool {
    return t.display
}

func (t *TestStruct) DisplayP() bool {
    return t.display
}

func TestNonPointerRecevier(t *testing.T) {
    v := &TestStruct{
        display: true,
    }

    go func() {
        v.OtherValue = 1
    }()
    go func() {
        fmt.Println(v.Display())
    }()
}

func TestPointerRecevier(t *testing.T) {
    v := &TestStruct{
        display: true,
    }

    go func() {
        v.OtherValue = 1
    }()
    go func() {
        fmt.Println(v.DisplayP())
    }()
}

With the pointer receiver method you have no error

go test -race -run ^TestPointerRecevier$
true
PASS
ok      _/Users/xxxxx/projects/golang/datarace  0.254s

You got this error when using the non-pointer receiver method

go test -race -run ^TestNonPointerRecevier$
==================
WARNING: DATA RACE
Read at 0x00c00001c2c8 by goroutine 9:
  _/Users/xxxxx/projects/golang/datarace.TestNonPointerRecevier.func2()
      /Users/xxxxx/projects/golang/datarace/main_test.go:30 +0x47

Previous write at 0x00c00001c2c8 by goroutine 8:
  _/Users/xxxxx/projects/golang/datarace.TestNonPointerRecevier.func1()
      /Users/xxxxx/projects/golang/datarace/main_test.go:27 +0x3e

Goroutine 9 (running) created at:
  _/Users/xxxxx/projects/golang/datarace.TestNonPointerRecevier()
      /Users/xxxxx/projects/golang/datarace/main_test.go:29 +0xba
  testing.tRunner()
      /usr/local/Cellar/go/1.15.6/libexec/src/testing/testing.go:1123 +0x202

Goroutine 8 (finished) created at:
  _/Users/xxxxx/projects/golang/datarace.TestNonPointerRecevier()
      /Users/xxxxx/projects/golang/datarace/main_test.go:26 +0x98
  testing.tRunner()
      /usr/local/Cellar/go/1.15.6/libexec/src/testing/testing.go:1123 +0x202
==================
true
FAIL
exit status 1
FAIL    _/Users/xxxxx/projects/golang/datarace  0.103s
kozmo
  • 4,024
  • 3
  • 30
  • 48
vreal
  • 797
  • 1
  • 7
  • 16
  • 4
    The entire value of `v`, including field `OtherValue`, is read when making the call with the value receiver. This is a data race with the write to field `OtherValue` in the other goroutine. The value of field `OtherValue` is not read when making the call with the pointer receiver. – Charlie Tumahai Jan 12 '21 at 05:25
  • Thanks, is that because Go creates a copy of v when using value receiver? – vreal Jan 12 '21 at 05:42

1 Answers1

0

When the receiver value of a method is a struct (and not a pointer to a struct), the complete struct is copied to be passed by value to that method.
So calling v.Display() implicitly reads field OtherValue (when making a copy of the struct), hence the race condition.

When using a pointer on the other hand, only the pointer is copied, and accessing v.display concurrently with v.OtherValue does not trigger a race condition.

LeGEC
  • 46,477
  • 5
  • 57
  • 104