I have a two slot array that need to swap between slots when producer set it and always return a valid slot to consumer. As for side of atomic operation logic I can't imagine situation when two goroutine write to the same array slot, but race detector think otherwise. Does anyone can explain me, where is the bug?
type checkConfig struct {
timeout time.Time
}
type checkConfigVersions struct {
config [2]*checkConfig
reader uint32
writer uint32
}
func (c *checkConfigVersions) get() *checkConfig {
return c.config[atomic.LoadUint32(&c.reader)]
}
func (c *checkConfigVersions) set(new *checkConfig) {
for {
reader := atomic.LoadUint32(&c.reader)
writer := atomic.LoadUint32(&c.writer)
switch diff := reader ^ writer; {
case diff == 0:
runtime.Gosched()
case diff == 1:
if atomic.CompareAndSwapUint32(&c.writer, writer, (writer+1)&1) {
c.config[writer] = new
atomic.StoreUint32(&c.reader, writer)
return
}
}
}
}
Data race happened on c.config[writer] = new
, but for my point of view it's not possible.
fun main() {
runtime.GOMAXPROCS(runtime.NumCPU())
var wg sync.WaitGroup
ccv := &checkConfigVersions{reader: 0, writer: 1}
for i := 0; i < runtime.NumCPU(); i++ {
wg.Add(100)
go func(i int) {
for j := 0; j < 100; j++ {
ccv.set(&checkConfig{})
wg.Done()
}
}(i)
}
wg.Wait()
fmt.Println(ccv.get())
}
Data race detector output:
==================
WARNING: DATA RACE
Write at 0x00c42009a020 by goroutine 12:
main.(*checkConfigVersions).set()
/Users/apple/Documents/Cyber/Go/proxy/main.go:118 +0xd9
main.main.func1()
/Users/apple/Documents/Cyber/Go/proxy/main.go:42 +0x60
Previous write at 0x00c42009a020 by goroutine 11:
main.(*checkConfigVersions).set()
/Users/apple/Documents/Cyber/Go/proxy/main.go:118 +0xd9
main.main.func1()
/Users/apple/Documents/Cyber/Go/proxy/main.go:42 +0x60
Goroutine 12 (running) created at:
main.main()
/Users/apple/Documents/Cyber/Go/proxy/main.go:40 +0x159
Goroutine 11 (running) created at:
main.main()
/Users/apple/Documents/Cyber/Go/proxy/main.go:40 +0x159
==================
And if you try to read it with ccv.read()
, you catch the another race but between read and write on the same array slot...