Golang's map type provides us a nice type for storing key-value pairs. With strong concurrent ability of golang's routines, developers can write concurrent programs to process multi-task easily. This article demonstrates different cases with golang map.
- Multiple Goroutines write to the same map simutaneously
package main
import (
"fmt"
"strconv"
"sync"
)
func writer1(m map[string]int, i int, wg *sync.WaitGroup) {
defer wg.Done()
m["writer1"+strconv.Itoa(i)] = i
}
func writer2(m map[string]int, i int, wg *sync.WaitGroup) {
defer wg.Done()
m["writer2"+strconv.Itoa(i)] = i
}
func main() {
m := make(map[string]int)
wg := sync.WaitGroup{}
for i := 0; i < 10000; i++ {
wg.Add(1)
go writer1(m, i, &wg)
wg.Add(1)
go writer2(m, i, &wg)
}
wg.Wait()
fmt.Println("all done")
}
Without surprise, above program will panic with error message as below:
fatal error: concurrent map writes
fatal error: concurrent map writes
goroutine 27 [running]:
runtime.throw(0x10ca778, 0x15)
/usr/local/go/src/runtime/panic.go:1117 +0x72 fp=0xc000104f00 sp=0xc000104ed0 pc=0x1032472
runtime.mapassign_faststr(0x10b21a0, 0xc00006e180, 0xc000180090, 0x9, 0x2)
- One Goroutine writes to map, as multiple Goroutines read from the same map
package main
import (
"fmt"
"strconv"
"sync"
)
func writer(m map[string]int, wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 10; i++ {
m["writer"+strconv.Itoa(i)] = i
}
}
func reader(m map[string]int, i int, wg *sync.WaitGroup) {
defer wg.Done()
k := "writer" + strconv.Itoa(i)
v, ok := m[k]
if !ok {
fmt.Println(fmt.Sprintf("%s has no value", k))
return
}
fmt.Println(fmt.Sprintf("%s has value %d in it.", k, v))
}
func main() {
m := make(map[string]int)
wg := sync.WaitGroup{}
wg.Add(1)
go writer(m, &wg)
for i := 0; i < 10; i++ {
wg.Add(1)
go reader(m, i, &wg)
}
wg.Wait()
fmt.Println("all done")
}
Code above will not always panic, but once it panics, errors below will be generated:
fatal error: concurrent map read and map write
goroutine 16 [running]:
runtime.throw(0x10ceb56, 0x21)
/usr/local/go/src/runtime/panic.go:1117 +0x72 fp=0xc00003f688 sp=0xc00003f658 pc=0x1032472
runtime.mapaccess2_faststr(0x10b3fc0, 0xc00006e180, 0xc000014080, 0x7, 0x1, 0xc000014080)
/usr/local/go/src/runtime/map_faststr.go:116 +0x4a5 fp=0xc00003f6f8 sp=0xc00003f688 pc=0x1011665
main.reader(0xc00006e180, 0x9, 0xc000014070)
/Users/wangfeng14/go/goproject/src/main.go:19 +0xee fp=0xc00003f7c8 sp=0xc00003f6f8 pc=0x10a4f6e
runtime.goexit()
/usr/local/go/src/runtime/asm_amd64.s:1371 +0x1 fp=0xc00003f7d0 sp=0xc00003f7c8 pc=0x1063fe1
created by main.main
/Users/wangfeng14/go/goproject/src/main.go:35 +0xdd
so concurrent map read and write is not allowed neither.
- Concurrent read from the same map simutaneously
package main
import (
"fmt"
"strconv"
"sync"
)
func writer(m map[string]int, wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 10; i++ {
m["writer"+strconv.Itoa(i)] = i
}
}
func reader(m map[string]int, i int, wg *sync.WaitGroup) {
defer wg.Done()
k := "writer" + strconv.Itoa(i)
v, ok := m[k]
if !ok {
fmt.Println(fmt.Sprintf("%s has no value", k))
return
}
fmt.Println(fmt.Sprintf("%s has value %d in it.", k, v))
}
func main() {
m := make(map[string]int)
wg1 := sync.WaitGroup{}
wg1.Add(1)
go writer(m, &wg1)
wg1.Wait()
wg2 := sync.WaitGroup{}
for i := 0; i < 10; i++ {
wg2.Add(1)
go reader(m, i, &wg2)
}
wg2.Wait()
fmt.Println("all done")
}
The code above can work normally.
What if we need to write the same map concurrently? There are two options we can choose,
a. use third party library which provides concurrent map for thread safety, such as concurrent-map
b. Wrap the map with a struct which contains a lock to make write the map mutually exclusive.