“fatal error: concurrent map writes” 是 Go 里最常见的线上崩溃之一。这篇在本机复现它:看 recover 能不能救、三种文字分别对应什么操作、运行时的检测会漏掉多少次、-race 补上多少,以及 Mutex、RWMutex、分片和 sync.Map 四种修法的吞吐。
ww 10 万次(退出码 2,用时 0.60 s):
fatal error: concurrent map writes
goroutine 34 [running]:
internal/runtime/maps.fatal({0x100610453?, 0x0?})
<GOROOT>/src/runtime/panic.go:1195 +0x20
main.main.func1(0x0)
<tmp>/m/main.go:11 +0x74
created by main.main in goroutine 1
<tmp>/m/main.go:11 +0x23c
main 和 goroutine 里都 recover(退出码 2,用时 0.31 s):
fatal error: concurrent map writes
goroutine 19 [running]:
internal/runtime/maps.fatal({0x102cef213?, 0x0?})
<GOROOT>/src/runtime/panic.go:1195 +0x20
main.main.func2(0x0)
…(共 8 行)fatal error,进程必崩,recover 无效。
两个写:10 次运行结果 {'fatal error: concurrent map writes': 10}
一个写一个读:10 次运行结果 {'fatal error: concurrent map read and map write': 10}
一个遍历一个写:10 次运行结果 {'fatal error: concurrent map iteration and map write': 10}writes、read and map write、iteration and map write,对应不同的访问组合。
每个 goroutine 写 10 次,GOMAXPROCS=默认:被发现 11/30,悄悄跑完 19/30(其中 len(m) 不等于应有的 20 的有 0 次) 每个 goroutine 写 100 次,GOMAXPROCS=默认:被发现 29/30,悄悄跑完 1/30(其中 len(m) 不等于应有的 200 的有 0 次) 每个 goroutine 写 1000 次,GOMAXPROCS=默认:被发现 30/30,悄悄跑完 0/30(其中 len(m) 不等于应有的 2000 的有 0 次) 每个 goroutine 写 10000 次,GOMAXPROCS=默认:被发现 30/30,悄悄跑完 0/30(其中 len(m) 不等于应有的 20000 的有 0 次) 每个 goroutine 写 100000 次,GOMAXPROCS=默认:被发现 30/30,悄悄跑完 0/30(其中 len(m) 不等于应有的 200000 的有 0 次) 每个 goroutine 写 100000 次,GOMAXPROCS=1:被发现 0/30,悄悄跑完 30/30(其中 len(m) 不等于应有的 200000 的有 0 次)
小数据量或单核时常漏,没崩溃不代表安全。
8 个 goroutine 只读 map,30 次运行:被报错 0/30
-race,每个 goroutine 写 10 次:10 次里报 DATA RACE 9/10
第一次报告的前几行(退出码 66):
==================
WARNING: DATA RACE
Write at 0x00c00018c120 by goroutine 7:
runtime.mapaccess2_fast64()
<GOROOT>/src/internal/runtime/maps/runtime_fast64.go:22 +0x2ac
main.main.func1()
<tmp>/mr/main.go:11 +0xb0
main.main.gowrap1()
<tmp>/mr/main.go:11 +0x38
-race,每个 goroutine 写 100000 次:10 次里报 DATA RACE 10/10只读并发安全;-race 报告率高得多但也不是 100%。
写占 1%:
sync.Mutex 9.6
sync.RWMutex 26.4
sync.Map 111.0
32 个分片+Mutex 38.1
写占 10%:
sync.Mutex 8.0
sync.RWMutex 25.2
sync.Map 82.8
32 个分片+Mutex 34.9
写占 50%:
sync.Mutex 6.5
sync.RWMutex 12.4
sync.Map 28.7
32 个分片+Mutex 27.6Mutex、RWMutex、分片、sync.Map,读多写少时 sync.Map 最快,写多时与分片接近。