Go语言map不支持并发写操作的原因

 更新时间:2024年01月01日 11:12:34   作者:frank  
Go语言为什么不支持并发读写map​,Go官方的说法是在多数情况下map只存在并发读操作,如果原生支持并发读写,即降低了并发读操作的性能,在使用 map 时,要特别注意是否存在对 map 的并发写操作,如果存在,要结合 sync 包的互斥锁一起使用,

本文介绍 Go 语言为什么不支持并发读写 map​,Go 官方的说法是在多数情况下 map 只存在并发读操作,如果原生支持并发读写,即降低了并发读操作的性能。在使用 map 时,要特别注意是否存在对 map 的并发写操作,如果存在,要结合 sync 包的互斥锁一起使用。

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.flags 和 hashWriting 的值,可知是否有其它 goroutine 访问 map,如果有,则返回致命错误 fatal("concurrent map writes")。

03 、总结

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

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

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

到此这篇关于Go语言map不支持并发写操作的原因的文章就介绍到这了,更多相关Go中map不支持并发写内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Go语言中http和mysql的实现代码

    Go语言中http和mysql的实现代码

    本文通过实例代码给大家介绍了Go语言中http和mysql的实现代码,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下
    2018-11-11
  • GoLang bytes.Buffer基础使用方法详解

    GoLang bytes.Buffer基础使用方法详解

    Go标准库中的bytes.Buffer(下文用Buffer表示)类似于一个FIFO的队列,它是一个流式字节缓冲区,我们可以持续向Buffer尾部写入数据,从Buffer头部读取数据。当Buffer内部空间不足以满足写入数据的大小时,会自动扩容
    2023-03-03
  • Golang中 import cycle not allowed 问题的解决方法

    Golang中 import cycle not allowed 问题

    这篇文章主要介绍了Golang中 import cycle not allowed 问题的解决方法,问题从描述到解决都非常详细,需要的小伙伴可以参考一下
    2022-03-03
  • Go基础系列:Go切片(分片)slice详解

    Go基础系列:Go切片(分片)slice详解

    这篇文章主要介绍了Go语言中的切片(分片)slice详细说明 ,需要的朋友可以参考下
    2022-04-04
  • 一文详解Golang中字符串的常见错误

    一文详解Golang中字符串的常见错误

    这篇文章主要来和大家深入讨论一下Golang 中的字符串,并查看一些不同的场景,以避免常见错误,对大家掌握golang有一定的帮助,需要的可以了解下
    2023-10-10
  • 浅析GO语言的垃圾回收机制

    浅析GO语言的垃圾回收机制

    今天我们来聊聊golang是如何进行垃圾回收的,我们知道,目前各语言进行垃圾回收的方法有很多,如引用计数、标记清除、分代回收、三色标记等,各种方式都有其特点,文中介绍的非常详细,感兴趣的小伙伴跟着小编一起学习吧
    2023-07-07
  • Golang中的错误处理的示例详解

    Golang中的错误处理的示例详解

    这篇文章主要为大家详细介绍了Golang中的错误处理的相关资料,文章中的示例代码讲解详细,对我们学习Golang有一定帮助,需要的可以参考一下
    2022-12-12
  • go简介及国内镜像源配置全过程

    go简介及国内镜像源配置全过程

    本文介绍了Go语言的基本概念和环境配置,包括GOROOT、GOPATH和GOMODULE的设置,还展示了如何在IDEA中配置Go语言的开发环境,并通过一个简单的“HelloWorld”项目来熟悉Go语言的基本语法和开发流程
    2025-01-01
  • 一文带你掌握Golang的反射基础

    一文带你掌握Golang的反射基础

    go的反射是由其标准库中的reflect包实现,该reflect包实现了在运行时进行反射的能力,本篇主要介绍了reflect的常用的几个方法,希望对大家有所帮助
    2023-02-02
  • 使用golang生成prometheus格式数据

    使用golang生成prometheus格式数据

    Prometheus是一个开源的监控系统,拥有许多Advanced Feature,本文将介绍Primetheus client的使用,并基于golang生成prometheus格式数据,希望对大家有所帮助
    2025-02-02

最新评论