ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
## 有并发问题的map 官方的faq已经提到内检的map不是线程(goroutune)安全的. 首先,让我们看一段并发读写的代码,下列程序中一个goroutine一直读,一个goroutine一只写同一个键值,即即使读写的键不相同,而且map也没有"扩容"等操作,代码还是会报错。 ```go package main func main() { m := make(map[int]int) go func() { for { _ = m[1] } }() go func() { for { m[2] = 2 } }() select {} } ``` 错误信息是: fatal error: concurrent map read and map write。 如果你查看Go的源代码: hashmap_fast.go#L118,会看到读的时候会检查hashWriting标志, 如果有这个标志,就会报并发错误。 ## sync.Map 特性: 1 空间换时间, 通过荣誉的两个数据结构(read, dirty), 实现加锁对性能的影响. 2 使用只读数据, 避免读写冲突 3 动态调整 miss次数多了以后, dirty数据提升为read 4 double checking 5 延迟删除, 删除只是打标记, 只有在提升dirty的时候才清除删除的数据 6 优先从read读取 更新 删除 7 虽然read 和dirty有冗余数据, 但这些书通过指针指向同一个数据. 所以空间有限. //该 Map 是线程安全的,读取,插入,删除也都保持着常数级的时间复杂度。 //多个 goroutines 协程同时调用 Map 方法也是线程安全的。该 Map 的零值是有效的, //并且零值是一个空的 Map 。线程安全的 Map 在第一次使用之后,不允许被拷贝 ## sync.map range 方法 因为for ... range map是内建的语言特性,所以没有办法使用for range遍历sync.Map, 但是可以使用它的Range方法,通过回调的方式遍历。 ```go func (m *Map) Range(f func(key, value interface{}) bool) { read, _ := m.read.Load().(readOnly) // 如果m.dirty中有新数据,则提升m.dirty,然后在遍历 if read.amended { //提升m.dirty m.mu.Lock() read, _ = m.read.Load().(readOnly) //双检查 if read.amended { read = readOnly{m: m.dirty} m.read.Store(read) m.dirty = nil m.misses = 0 } m.mu.Unlock() } // 遍历, for range是安全的 for k, e := range read.m { v, ok := e.load() if !ok { continue } if !f(k, v) { break } } } ```