在golang中,map不是并发安全的。
经过测试发现concurrent map writes 是无法被recover捕获的。经查看源码发现,concurrent map writes是用throw抛出的。
// runtime/map.go
if h.flags&hashWriting != 0 {
throw("concurrent map writes")
}
然后,再继续往下追踪,发现throw会强制退出程序。
// runtime/panic.go
func throw(s string) {
// Everything throw does should be recursively nosplit so it
// can be called even when it's unsafe to grow the stack.
systemstack(func() {
print("fatal error: ", s, "\n")
})
gp := getg()
if gp.m.throwing == 0 {
gp.m.throwing = 1
}
// 调用fatalthrow
fatalthrow()
*(*int)(nil) = 0 // not reached
}
// fatalthrow implements an unrecoverable runtime throw. It freezes the
// system, prints stack traces starting from its caller, and terminates the
// process.
// 翻译:fatalthrow 实现了一个不可recover的运行时throw。冻结系统,同时从调用处开始打印栈链信息,并退出程序。
//go:nosplit
func fatalthrow() {
pc := getcallerpc()
sp := getcallersp()
gp := getg()
// Switch to the system stack to avoid any stack growth, which
// may make things worse if the runtime is in a bad state.
systemstack(func() {
startpanic_m()
if dopanic_m(gp, pc, sp) {
// crash uses a decent amount of nosplit stack and we're already
// low on stack in throw, so crash on the system stack (unlike
// fatalpanic).
crash()
}
exit(2)
})
*(*int)(nil) = 0 // not reached
}
可以看到throw抛出的异常是不可recover的
这里学习了一个重要的知识点:runtime.panic和runtime.throw。感兴趣的可以自行查询两者区别。
那么为什么这里使用throw,而不是用panic呢?
map能够检测到数据竞争,但是当检测到数据竞争时,map的内部状态已经被破坏。所以runtime使用throw抛出异常,并结束程序。
那么,在golang中到底还有哪些这样的异常是不可捕获的呢?
详细的讨论,在这里查看:https://stackoverflow.com/questions/57486620/are-all-runtime-errors-recoverable-in-go
关于为什么使用throw,而不是panic的详细的讨论,可以查看我在stackoverflow上的提问(虽然问题暂时被关闭了):https://stackoverflow.com/questions/68067330/what-would-happen-if-runtime-does-not-throwconcurrent-map-writes-but-panic
以上,记录一下。
知识点:map,data race,runtime.throw