Golang BugFix: fatal error: concurrent map writes

问题

今天原本运行正常的程序,突然爆以下错误:

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()
}

参考文档

https://golang.org/pkg/sync/#Map

0 评论
最新
最旧 最多投票
内联反馈
查看所有评论
滚动至顶部