-2

I’m trying to update a map from multiple goroutines, but the results seem inconsistent. Sometimes keys are missing or values are wrong.

Here’s my code: package main

import (
    "fmt"
    "sync"
)

func main() {
    counter := make(map[string]int)
    var wg sync.WaitGroup

    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            key := fmt.Sprintf("key%d", id)
            counter[key] = id * 10
        }(i)
    }

    wg.Wait()
    fmt.Println(counter)
}

Sometimes the output is missing some keys or has incorrect values.

I tried adding time.Sleep() inside the goroutine, but that doesn’t fix it reliably.

3
  • 2
    Map (nor any value in Go) is not safe for concurrent read/write. Don't do that. You should expect nothing from your code without proper synchronization. Commented Oct 29 at 12:53
  • 2
    Sleep is not a synchronization primitive. You must always synchronize concurrent reads and writes. Commented Oct 29 at 13:39
  • 2
    It looks like that you thought that synchronizing the main goroutines with the ones spawned in the loop is enough (fmt.Println is guaranteed to be called after all the goroutines finish) but it is not because the goroutines spawned in the loop are executed concurrently with each other, and they all mutate the map. So should you run your example after building it with go build -race, you would see the Go runtime crash your program almost immediately, reporting a data race. Go has no concurrency-safe builtin types, and most 3rd-party types are not concurrency-safe either, unless documented. Commented Oct 29 at 15:31

1 Answer 1

4

As said above, maps are not safe inside go routines like this.

The simplest way to modify your example is to add a mutex and perform lock/unlock on each pass.

package main

import (
    "fmt"
    "sync"
)

type Counter struct {
    m  map[string]int
    mu sync.Mutex
}

func main() {
    counter := Counter{m: make(map[string]int)}
    var wg sync.WaitGroup

    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            key := fmt.Sprintf("key%d", id)

            counter.mu.Lock()
            defer counter.mu.Unlock()
            counter.m[key] = id * 10
        }(i)
    }

    wg.Wait()
    fmt.Println(counter.m)
}

This should help demonstrate the issue. But there are other ways to handle this, with read/write mutex or a channel. Note that using locks may slow down the program, compared to other methods.

More discussion: How safe are Golang maps for concurrent Read/Write operations?

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.