Golang中并发操作map会导致panic或其他问题吗?
Golang中并发操作map会导致panic或其他问题吗? 在某些场景中,配置会定期加载到映射中,并且对实时性没有要求,这意味着允许读取过时的数据。
- 这种并发操作是否会导致恐慌或其他问题?
- 这种操作能否确保我在一段时间后能够读取到新加载的数据? (例如:在 0 秒时加载了新数据, 在 0 秒读取数据时,可能会返回旧数据, 但在 3 秒读取数据时,必须返回新数据。)
// 全局变量
var myMap map[int]string
...
// 定期加载
go func() {
tmpMap := loadMap()
myMap = tmpMap
} ()
// 读取
go func() {
...
v, ok := myMap[key]
...
} ()
更详细地说,如果我将映射替换为一个包含映射的结构体,是否可行?如下所示:
type myStruct struct {
m map[int]string
}
// 全局变量
var s *myStruct
...
// 定期加载
go func() {
tmp := loadStruct()
s = tmp
} ()
// 读取
go func() {
...
v, ok := s.m[key]
...
} ()
更多关于Golang中并发操作map会导致panic或其他问题吗?的实战教程也可以访问 https://www.itying.com/category-94-b0.html
sqrt7:
然而,我担心使用互斥锁或通道可能会延长读取时间。
影响不会很显著。这完全取决于你如何编写 read 处理代码。此外,锁或通道传输是 goroutine 之间同步所必需的代价。
那种许多并发进程使锁饱和的极端情况是另一个问题(通常与你整体的算法有关)。在断定这种担忧对于非实时应用程序是否成立之前,你需要在你的硬件上用测试代码进行实验。
无论如何,由于数据竞争条件,你必须同步所有 3 个 goroutine。
sqrt7:
如果使用互斥锁和通道,我认为请求其他数据的过程可能会被阻塞,因为此时映射可能正在被写入并被互斥锁锁定。
这完全与你如何在同步期间处理等待有关。正如我之前提到的,read 必须知道如何处理数据不存在、数据过时、数据损坏、何时超时等待、是否应该重试等情况。
无论是同步还是通道,在等待期间都不会像“延迟计数器算法”那样占用 CPU 资源,所以不用担心。
sqrt7:
m.Lock() defer m.UnLock() // write operation
这是我的默认做法,因为在编写函数/方法时,你不需要在脑海中追踪锁的状态。defer 确实是个非常好的伙伴。
sqrt7:
m.Lock() // write operation m.UnLock
这种方式仅在我需要根据条件手动控制锁,或者需要将锁传递到其他地方时才会使用。例如:
- 如果我遇到一些错误,我会将锁作为参数完整地传递给我的自定义
handleError(...)函数,然后由handleError(...)来执行解锁步骤。
使用这种方式时,你需要像在其他编程语言中一样,在开发函数/方法时追踪锁的状态。
常见的错误通常是:
- 忘记解锁,或者
- 没有在脑海中检查代码中锁的状态。
编辑 1:
这种方法也用于高并发访问的场景(例如,变量被频繁地读写)。defer 只会在函数结束后才执行,所以如果你的函数非常繁忙,锁定的时间会不必要地延长。
是的,并发操作 map 确实会导致 panic 或其他问题。在 Go 中,map 不是并发安全的,多个 goroutine 同时读写同一个 map 会导致 panic。
对于你提供的两种方案:
-
直接替换 map:这种方案是安全的,因为 map 的赋值是原子的。读取操作可能会读到旧数据,但不会 panic。
-
替换包含 map 的结构体指针:这种方案也是安全的,指针的赋值是原子的。读取操作同样可能读到旧数据,但不会 panic。
两种方案都能确保在一段时间后读取到新数据。由于 Go 的内存模型保证,一旦 goroutine 观察到新的指针值,它就能看到该指针指向的完整数据结构。
示例代码:
// 方案1:直接替换map
var myMap map[int]string
func updateMap() {
tmpMap := loadMap()
myMap = tmpMap // 原子操作
}
func readMap(key int) (string, bool) {
m := myMap // 获取当前map的快照
v, ok := m[key]
return v, ok
}
// 方案2:替换结构体指针
type myStruct struct {
m map[int]string
}
var s *myStruct
func updateStruct() {
tmp := loadStruct()
s = tmp // 原子操作
}
func readStruct(key int) (string, bool) {
current := s // 获取当前结构体的快照
if current == nil {
return "", false
}
v, ok := current.m[key]
return v, ok
}
需要注意的是,虽然这两种方案避免了并发 panic,但读取操作可能获取到不一致的数据快照。如果你需要更强的数据一致性保证,可以考虑使用 sync.RWMutex 或 sync.Map。


