Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

According to the Go blog,

Maps are not safe for concurrent use: it's not defined what happens when you read and write to them simultaneously. If you need to read from and write to a map from concurrently executing goroutines, the accesses must be mediated by some kind of synchronization mechanism. (source: https://blog.golang.org/go-maps-in-action)

Can anyone elaborate on this? Concurrent read operations seem permissible across routines, but concurrent read/write operations may generate a race condition if one attempts to read from and write to the same key.

Can this last risk be reduced in some cases? For example:

  • Function A generates k and sets m[k]=0. This is the only time A writes to map m. k is known to not be in m.
  • A passes k to function B running concurrently
  • A then reads m[k]. If m[k]==0, it waits, continuing only when m[k]!=0
  • B looks for k in the map. If it finds it, B sets m[k] to some positive integer. If it doesn't it waits until k is in m.

This isn't code (obviously) but I think it shows the outlines of a case where even if A and B both try to access m there won't be a race condition, or if there is it won't matter because of the additional constraints.

like image 587
John D. Avatar asked Mar 22 '16 23:03

John D.


3 Answers

Before Golang 1.6, concurrent read is OK, concurrent write is not OK, but write and concurrent read is OK. Since Golang 1.6, map cannot be read when it's being written. So After Golang 1.6, concurrent access map should be like:

package main

import (
    "sync"
    "time"
)

var m = map[string]int{"a": 1}
var lock = sync.RWMutex{}

func main() {
    go Read()
    time.Sleep(1 * time.Second)
    go Write()
    time.Sleep(1 * time.Minute)
}

func Read() {
    for {
        read()
    }
}

func Write() {
    for {
        write()
    }
}

func read() {
    lock.RLock()
    defer lock.RUnlock()
    _ = m["a"]
}

func write() {
    lock.Lock()
    defer lock.Unlock()
    m["b"] = 2
}

Or you will get the error below: enter image description here

ADDED:

You can detect the race by using go run -race race.go

Change the read function:

func read() {
    // lock.RLock()
    // defer lock.RUnlock()
    _ = m["a"]
}

enter image description here

Another choise:

As we known, map was implemented by buckets and sync.RWMutex will lock all the buckets. concurrent-map use fnv32 to shard the key and every bucket use one sync.RWMutex.

like image 168
Bryce Avatar answered Oct 13 '22 05:10

Bryce


Concurrent read (read only) is ok. Concurrent write and/or read is not ok.

Multiple goroutines can only write and/or read the same map if access is synchronized, e.g. via the sync package, with channels or via other means.

Your example:

  1. Function A generates k and sets m[k]=0. This is the only time A writes to map m. k is known to not be in m.
  2. A passes k to function B running concurrently
  3. A then reads m[k]. If m[k]==0, it waits, continuing only when m[k]!=0
  4. B looks for k in the map. If it finds it, B sets m[k] to some positive integer. If it doesn't it waits until k is in m.

Your example has 2 goroutines: A and B, and A tries to read m (in step 3) and B tries to write it (in step 4) concurrently. There is no synchronization (you didn't mention any), so this alone is not permitted / not determined.

What does it mean? Not determined means even though B writes m, A may never observe the change. Or A may observe a change that didn't even happen. Or a panic may occur. Or the Earth may explode due to this non-synchronized concurrent access (although the chance of this latter case is extremely small, maybe even less than 1e-40).

Related questions:

Map with concurrent access

what does not being thread safe means about maps in Go?

What is the danger of neglecting goroutine/thread-safety when using a map in Go?

like image 35
icza Avatar answered Oct 13 '22 05:10

icza


Go 1.6 Release Notes

The runtime has added lightweight, best-effort detection of concurrent misuse of maps. As always, if one goroutine is writing to a map, no other goroutine should be reading or writing the map concurrently. If the runtime detects this condition, it prints a diagnosis and crashes the program. The best way to find out more about the problem is to run the program under the race detector, which will more reliably identify the race and give more detail.

Maps are complex, self-reorganizing data structures. Concurrent read and write access is undefined.

Without code, there's not much else to say.

like image 27
peterSO Avatar answered Oct 13 '22 05:10

peterSO