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.
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.
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
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!