如何为Golang中的并发读/写锁定特定的地图索引

I wanted to know if there is a way to lock only an index in a map during concurrent read/write. I am pretty new to Golang and parallelism sorry if the answer is obvious.

func Check(a, b []string) map[string]int {
    var res = make(map[string]int)
    go func() {
        for _, v := range a {
            res[v]++
        }
    }()
    go func() {
        for _, v := range b {
            res[v]++
        }
    }()
    return res
}

Eventually this piece of code is going to panic due to concurrent map Read/Write. So we should add mutex to lock the map.

var m sync.Mutex
go func() {
    for _, v := range a {
        m.Lock()
        res[v]++
        m.Unlock()
    }
}()
go func() {
    for _, v := range b {
        m.Lock()
        res[v]++
        m.Unlock()
    }
}()

But from my understanding m.lock() will lock my whole map? Isn't this too much overhead by locking everything? This bugged me as i thought this piece of code may not be faster than running linearly. Can I possibly lock only the map at map["some key"], so that my second goroutine can still write in map["some other key"]?

Maps themselves do not take care of locks, therefore any manipulation of them on multiple go routines (or reading while they are being manipulated) will require some for of syncing (e.g., sync.Mutex). There are fancier things you can do though.

RW Mutex

You can get a little fancier depending on your use case and use a sync.RWMutex. This will allow concurrent reads while safely blocking for any write. For example:

package main

import (
    "sync"
    "time"
)

func main() {
    m := map[int]int{}
    lock := sync.RWMutex{}

    go func() {
        // Writer
        for range time.Tick(250 * time.Millisecond) {
            // Notice that this uses Lock and NOT RLock
            lock.Lock()
            m[5]++
            m[6] += 2
            lock.Unlock()
        }
    }()

    go func() {
        for range time.Tick(250 * time.Millisecond) {
            lock.RLock()
            println(m[5])
            lock.RUnlock()
        }
    }()

    for range time.Tick(250 * time.Millisecond) {
        lock.RLock()
        println(m[6])
        lock.RUnlock()
    }
}

This does not give you a key by key locking mechanism though.

sync.Map

The sync.Map is provided by the standard library and is robust. It has more fine grained locking.

package main

import (
    "sync"
    "time"
)

func main() {
    m := sync.Map{}

    go func() {
        // Writer
        for range time.Tick(250 * time.Millisecond) {
            value, _ := m.LoadOrStore(5, 0)
            m.Store(5, value.(int)+1)
            value, _ = m.LoadOrStore(6, 0)
            m.Store(6, value.(int)+2)
        }
    }()

    go func() {
        for range time.Tick(250 * time.Millisecond) {
            value, _ := m.LoadOrStore(5, 0)
            println(value.(int))
        }
    }()

    for range time.Tick(250 * time.Millisecond) {
        value, _ := m.LoadOrStore(6, 0)
        println(value.(int))
    }
}

Notice that the code doesn't have any mutexes. Also notice that you get to deal with empty interfaces...

From GO 1.9 Release notes

Concurrent Map

The new Map type in the sync package is a concurrent map with amortized-constant-time loads, stores, and deletes. It is safe for multiple goroutines to call a Map's methods concurrently.

GO Team built one for you!