0

in below example, the race detector will trigger an error. I am fine with it, though, as it does not change keys, the map header (if i might say), i struggle figuring out what is the reason of the race. I simply dont understand what is going on under hood so that a race detection is emitted.

package main

import (
    "fmt"
    "sync"
)

// scores holds values incremented by multiple goroutines.
var scores = make(map[string]int)

func main() {
    var wg sync.WaitGroup
    wg.Add(2)
    scores["A"] = 0
    scores["B"] = 0
    go func() {
        for i := 0; i < 1000; i++ {
            // if _, ok := scores["A"]; !ok {
            //  scores["A"] = 1
            // } else {
            scores["A"]++
            // }
        }
        wg.Done()
    }()

    go func() {
        for i := 0; i < 1000; i++ {
            scores["B"]++ // Line 28
        }
        wg.Done()
    }()

    wg.Wait()
    fmt.Println("Final scores:", scores)
}
  • I guess this answer fix your problem https://stackoverflow.com/a/35511833/4994352 and more info on the map in golang https://dave.cheney.net/2018/05/29/how-the-go-runtime-implements-maps-efficiently-without-generics – Alexandre Catalano Nov 07 '20 at 18:35
  • @AlexandreCatalano no I am not seeking for the way to fix this. I already know how to synchronize access to this resource. –  Nov 07 '20 at 18:36
  • @Inian yes i know about what you are refering to. I have taken that example in regards to what we can do using slices. Please try https://play.golang.org/p/9aImwG6joYg –  Nov 07 '20 at 18:38
  • 2
    The runtime is free to rearrange map contents in memory with every update, regardless of whether the key exists or not. I seem to remember reading somewhere that this allows for optimizations (moving frequently accessed elements to the beginning of their bins, for instance). Unfortunately I can't for the life of me find a link to that statement. – Peter Nov 07 '20 at 18:53
  • 1
    see also and particularly https://stackoverflow.com/a/49202499/4466350 –  Nov 08 '20 at 13:09

1 Answers1

2

Map values are not addressable, so incrementing the integer values requires writing them back to the map itself.

The line

scores["A"]++

is equivalent to

tmp := scores["A"]
scores["A"] = tmp + 1

If you use a pointer to make the integer values addressable, and assign all the keys before the goroutines are dispatched, you can see there is no longer a race on the map itself:

var scores = make(map[string]*int)

func main() {
    var wg sync.WaitGroup
    wg.Add(2)
    scores["A"] = new(int)
    scores["B"] = new(int)
    go func() {
        for i := 0; i < 1000; i++ {
            (*scores["A"])++
        }
        wg.Done()
    }()

    go func() {
        for i := 0; i < 1000; i++ {
            (*scores["B"])++
        }
        wg.Done()
    }()

    wg.Wait()
    fmt.Println("Final scores:", scores)
}
JimB
  • 104,193
  • 13
  • 262
  • 255