Go 语言为什么不支持并发读写 map?

简介: Go 语言为什么不支持并发读写 map?

01


介绍


在 Go 语言项目开发中,我们经常会使用哈希表 map,它的时间复杂度是 O(1),Go 语言中的 map 使用开放寻址法避免哈希碰撞。


Go 语言中的 map 并非原子操作,不支持并发读写操作。


Go 官方认为 map 在大多数情况下是使用 map 进行并发读操作,仅在少数情况下是使用 map 进行并发读写操作。


如果 Go 语言中的 map 原生支持并发读写操作,在操作时需要先获取互斥锁,反而会降低只有并发读操作时的性能。


在需要并发读写操作 map 时,可以结合 sync 包中的互斥锁一起使用。


02


并发读写 map


Go 支持并发读 map,不支持并发读写 map


示例代码:


func main() {
 var m = make(map[int]string)
 go func() {
  for {
   m[1] = "xx"
  }
 }()
 go func() {
  for {
   _ = m[1]
  }
 }()
 time.Sleep(time.Second * 3)
}


输出结果:


fatal error: concurrent map read and map write
// ...


阅读上面这段代码,我们并发读写 map 类型的变量 m,在运行时,返回致命错误 fatal error: concurrent map read and map write



Go 语言中的 map 在运行时是怎么检测到 map 的存在写操作?

源码:

const (
 // flags
 iterator     = 1 // there may be an iterator using buckets
 oldIterator  = 2 // there may be an iterator using oldbuckets
 hashWriting  = 4 // a goroutine is writing to the map
 sameSizeGrow = 8 // the current map growth is to a new map of the same size
)
// A header for a Go map.
type hmap struct {
 count     int // # live cells == size of map.  Must be first (used by len() builtin)
 flags     uint8
 B         uint8  // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
 noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
 hash0     uint32 // hash seed
 buckets    unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
 oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
 nevacuate  uintptr        // progress counter for evacuation (buckets less than this have been evacuated)
 extra *mapextra // optional fields
}
// Like mapaccess, but allocates a slot for the key if it is not present in the map.
func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
 // ...
done:
 if h.flags&hashWriting == 0 {
  fatal("concurrent map writes")
 }
 h.flags &^= hashWriting
 if t.IndirectElem() {
  elem = *((*unsafe.Pointer)(elem))
 }
 return elem
}


阅读上面这段源码,我们可以发现在 hmap 结构体中的字段 flags,该字段用于标记 map 是否为写入状态。


在访问 map 时,通过判断 hmap.flagshashWriting 的值,可知是否有其它 goroutine 访问 map,如果有,则返回致命错误 fatal("concurrent map writes")


03


总结


本文介绍 Go 语言为什么不支持并发读写 map,Go 官方的说法是在多数情况下 map 只存在并发读操作,如果原生支持并发读写,即降低了并发读操作的性能。


通过阅读源码,我们了解到在运行时检测是否存在其它 goroutine map 的写操作,如果存在,则返回致命错误。


读者朋友们在使用 map 时,要特别注意是否存在对 map 的并发写操作,如果存在,要结合 sync 包的互斥锁一起使用。

相关文章
|
4月前
|
存储 JavaScript Java
(Python基础)新时代语言!一起学习Python吧!(四):dict字典和set类型;切片类型、列表生成式;map和reduce迭代器;filter过滤函数、sorted排序函数;lambda函数
dict字典 Python内置了字典:dict的支持,dict全称dictionary,在其他语言中也称为map,使用键-值(key-value)存储,具有极快的查找速度。 我们可以通过声明JS对象一样的方式声明dict
325 1
|
4月前
|
存储 安全 Java
【Golang】(4)Go里面的指针如何?函数与方法怎么不一样?带你了解Go不同于其他高级语言的语法
结构体可以存储一组不同类型的数据,是一种符合类型。Go抛弃了类与继承,同时也抛弃了构造方法,刻意弱化了面向对象的功能,Go并非是一个传统OOP的语言,但是Go依旧有着OOP的影子,通过结构体和方法也可以模拟出一个类。
293 1
|
6月前
|
Cloud Native 安全 Java
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
409 1
|
8月前
|
存储 Go 开发者
Go 语言中如何处理并发错误
在 Go 语言中,并发编程中的错误处理尤为复杂。本文介绍了几种常见的并发错误处理方法,包括 panic 的作用范围、使用 channel 收集错误与结果,以及使用 errgroup 包统一管理错误和取消任务,帮助开发者编写更健壮的并发程序。
177 4
Go 语言中如何处理并发错误
|
7月前
|
存储 人工智能 安全
深入理解 go sync.Map - 基本原理
本文介绍了 Go 语言中 `map` 在并发使用时的常见问题及其解决方案,重点对比了 `sync.Mutex`、`sync.RWMutex` 和 `sync.Map` 的性能差异及适用场景。文章指出,普通 `map` 不支持并发读写,容易引发错误;而 `sync.Map` 通过原子操作和优化设计,在某些场景下能显著提升性能。同时详细讲解了 `sync.Map` 的基本用法及其适合的应用环境,如读多写少或不同 goroutine 操作不同键的场景。
320 1
|
6月前
|
Cloud Native Go API
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
480 0
|
6月前
|
Cloud Native Java Go
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
321 0
|
6月前
|
Cloud Native Java 中间件
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
354 0
|
6月前
|
Cloud Native Java Go
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
388 0
|
6月前
|
数据采集 Go API
Go语言实战案例:多协程并发下载网页内容
本文是《Go语言100个实战案例 · 网络与并发篇》第6篇,讲解如何使用 Goroutine 和 Channel 实现多协程并发抓取网页内容,提升网络请求效率。通过实战掌握高并发编程技巧,构建爬虫、内容聚合器等工具,涵盖 WaitGroup、超时控制、错误处理等核心知识点。

热门文章

最新文章