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 包的互斥锁一起使用。

相关文章
|
9天前
|
存储 JSON 监控
Viper,一个Go语言配置管理神器!
Viper 是一个功能强大的 Go 语言配置管理库,支持从多种来源读取配置,包括文件、环境变量、远程配置中心等。本文详细介绍了 Viper 的核心特性和使用方法,包括从本地 YAML 文件和 Consul 远程配置中心读取配置的示例。Viper 的多来源配置、动态配置和轻松集成特性使其成为管理复杂应用配置的理想选择。
29 2
|
7天前
|
Go 索引
go语言中的循环语句
【11月更文挑战第4天】
19 2
|
7天前
|
Go C++
go语言中的条件语句
【11月更文挑战第4天】
20 2
|
10天前
|
监控 Go API
Go语言在微服务架构中的应用实践
在微服务架构的浪潮中,Go语言以其简洁、高效和并发处理能力脱颖而出,成为构建微服务的理想选择。本文将探讨Go语言在微服务架构中的应用实践,包括Go语言的特性如何适应微服务架构的需求,以及在实际开发中如何利用Go语言的特性来提高服务的性能和可维护性。我们将通过一个具体的案例分析,展示Go语言在微服务开发中的优势,并讨论在实际应用中可能遇到的挑战和解决方案。
|
7天前
|
Go
go语言中的 跳转语句
【11月更文挑战第4天】
17 4
|
7天前
|
JSON 安全 Go
Go语言中使用JWT鉴权、Token刷新完整示例,拿去直接用!
本文介绍了如何在 Go 语言中使用 Gin 框架实现 JWT 用户认证和安全保护。JWT(JSON Web Token)是一种轻量、高效的认证与授权解决方案,特别适合微服务架构。文章详细讲解了 JWT 的基本概念、结构以及如何在 Gin 中生成、解析和刷新 JWT。通过示例代码,展示了如何在实际项目中应用 JWT,确保用户身份验证和数据安全。完整代码可在 GitHub 仓库中查看。
39 1
|
11天前
|
Go 数据处理 API
Go语言在微服务架构中的应用与优势
本文摘要采用问答形式,以期提供更直接的信息获取方式。 Q1: 为什么选择Go语言进行微服务开发? A1: Go语言的并发模型、简洁的语法和高效的编译速度使其成为微服务架构的理想选择。 Q2: Go语言在微服务架构中有哪些优势? A2: 主要优势包括高性能、高并发处理能力、简洁的代码和强大的标准库。 Q3: 文章将如何展示Go语言在微服务中的应用? A3: 通过对比其他语言和展示Go语言在实际项目中的应用案例,来说明其在微服务架构中的优势。
|
11天前
|
Go 数据处理 调度
探索Go语言的并发模型:Goroutines与Channels的协同工作
在现代编程语言中,Go语言以其独特的并发模型脱颖而出。本文将深入探讨Go语言中的Goroutines和Channels,这两种机制如何协同工作以实现高效的并发处理。我们将通过实际代码示例,展示如何在Go程序中创建和管理Goroutines,以及如何使用Channels进行Goroutines之间的通信。此外,本文还将讨论在使用这些并发工具时可能遇到的常见问题及其解决方案,旨在为Go语言开发者提供一个全面的并发编程指南。
|
9天前
|
Go 调度 开发者
探索Go语言中的并发模式:goroutine与channel
在本文中,我们将深入探讨Go语言中的核心并发特性——goroutine和channel。不同于传统的并发模型,Go语言的并发机制以其简洁性和高效性著称。本文将通过实际代码示例,展示如何利用goroutine实现轻量级的并发执行,以及如何通过channel安全地在goroutine之间传递数据。摘要部分将概述这些概念,并提示读者本文将提供哪些具体的技术洞见。
|
2月前
|
Go 定位技术 索引
Go 语言Map(集合) | 19
Go 语言Map(集合) | 19