问题
今天原本运行正常的程序,突然爆以下错误:
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() }