问题
今天原本运行正常的程序,突然爆以下错误:
fatal error: concurrent map writes
fatal error: concurrent map writes
goroutine 8 [running]:
runtime.throw(0x1147862, 0x15)
/usr/local/go/src/runtime/panic.go:1116 +0x72 fp=0xc00003f740 sp=0xc00003f710 pc=0x1034f12
runtime.mapassign_fast64(0x1121720, 0xc000010300, 0x1, 0x0)
/usr/local/go/src/runtime/map_fast64.go:176 +0x325 fp=0xc00003f780 sp=0xc00003f740 pc=0x1011c65
command-line-arguments.RunGo.func1(0xc00001a110, 0xc000010300, 0x1)
/Users/ethanxu/Ethan/Learn/go_learning/src/other/other_test.go:16 +0x6d fp=0xc00003f7c8 sp=0xc00003f780 pc=0x110b36d
runtime.goexit()
/usr/local/go/src/runtime/asm_amd64.s:1374 +0x1 fp=0xc00003f7d0 sp=0xc00003f7c8 pc=0x1069861
created by command-line-arguments.RunGo
/Users/ethanxu/Ethan/Learn/go_learning/src/other/other_test.go:14 +0x9b
原因
经过排查,原因是因为在go中map是非线程安全的,并发写会产生竞争,多个协程同时写就会出现fatal error: concurrent map writes的错误,如以下程序, 执行就会报该错误:
func RunGo() {
emap := make(map[int]int)
i := 0
wg := sync.WaitGroup{}
for i < 10 {
wg.Add(1)
go func(i int) {
defer wg.Done()
emap[i] = i
}(i)
i++
}
wg.Wait()
return
}
func TestMap(t *testing.T) {
RunGo()
}
因为map为引用类型,所以即使函数传值调用,参数副本依然指向映射m, 所以多个goroutine并发写同一个映射m, 写过多线程程序的同学都知道,对于共享变量,资源,并发读写会产生竞争的, 故共享资源遭到破坏。
而本次报错的线上服务还比较隐晦,是协程中调用redis服务,而redis服务本身会写日志,而写日志的方法用到了map,所以导致线上爆出了该问题。
解决
改用并发安全的sync.map, 如:
func RunGoV2() {
emap := sync.Map{}
i := 0
wg := sync.WaitGroup{}
for i < 10 {
wg.Add(1)
go func(i int) {
defer wg.Done()
emap.Store(i, i)
}(i)
i++
}
wg.Wait()
}
func TestMap(t *testing.T) {
RunGoV2()
}


