Go语言中的map为什么默认不是并发安全的?

简介: Go语言的map默认不保证并发安全,以优化性能和简洁性。官方建议在需要时使用`sync.Mutex`保证安全。从Go 1.6起,并发读写map会导致程序崩溃,鼓励开发者显式处理并发问题。这样做的哲学是让代码更清晰,并避免不必要的性能开销。

今天我们聊一个 Go 语言中的 “热门” 话题——为什么 Go 语言中的 map 默认不是并发安全的呢?

对于广大 Go 程序员来说,尤其是那些刚跨入 Go 世界的新朋友们,这个问题或许让你们摸不着头脑。别急,让我们一起慢慢揭开这层神秘的面纱。

Go 语言中 map 的基本使用

首先,我们得知道 map 是什么。在 Go 中,map 是一种内置的数据结构,它提供了 “键值对”(Key-Value)的存储机制。使用 map,你可以通过 Key 快速找到对应的 Value,这让我们在处理一些需要快速查询的场景时如虎添翼。

一个简单的 map 示例:

package main

import "fmt"

func main() {
   
    // 创建一个map
    myMap := make(map[string]int)
    // 向map中添加键值对
    myMap["apple"] = 1
    myMap["banana"] = 2

    // 从map中获取值
    fmt.Println(myMap["apple"]) // 输出: 1
}

那为什么 map 默认不是并发安全的呢?

难不成 Go 官方觉得太复杂了?性能太差了?还是为了什么?

典型使用场景

Go 官方认为,map 的典型使用场景并不需要从多个 goroutine 中安全地访问。因此,在设计时,优先考虑了性能和简单性,而没有将并发安全作为默认特性。这是一种基于使用案例进行权衡的结果。

性能考量

引入并发安全意味着每次操作 map 时都需要进行加锁和解锁,这无疑会增加额外的性能开销。为了大多数程序的性能考虑,Go 没有将 map 设计为并发安全的,因为这会导致即使在不需要并发访问的场景下,也要付出不必要的性能代价。

官方方案

Go 1.6 开始,引入了并发访问 map 的检测机制,如果检测到并发读写,程序会直接崩溃,而不是隐瞒问题。Go 官方倾向于让问题显露出来("let it crash"),这样可以迫使开发者正视并发问题,采取正确的方法来解决。

如何安全地在多个 goroutine 中操作 map?

虽然原生的 map 不是并发安全的,但 Go 提供了其他机制来解决并发访问的问题。最直接的方法是使用互斥锁 sync.Mutex,来确保同一时间只有一个 goroutine 能访问 map。

当然现在不止这一个方法保证 map 并发安全,由于篇幅有限,这里仅以此为例。

例子如下:

package main

import (
    "fmt"
    "sync"
)

var (
    myMap = make(map[string]int)
    lock  sync.Mutex
)

func main() {
   
    // 启动一个 goroutine 写入数据
    go func() {
   
        for {
   
            lock.Lock()
            myMap["apple"] = 1
            lock.Unlock()
        }
    }()

    // 在主 goroutine 中读取数据
    for {
   
        lock.Lock()
        fmt.Println(myMap["apple"])
        lock.Unlock()
    }
}

结论

通过以上的探讨,我们了解了为什么 Go 语言中的 map 默认不是并发安全的,其实就是一句话概括:Go 官方觉得大部分场景都不需要支持并发,从性能上做的考虑

Go 语言的设计哲学之一就是简单而有效,通过让开发者显式地处理并发问题,既保证了性能,也让代码的行为更加透明。

也有网友讨论说,可以像 Java 那样提供两个 map,一个支持并发,性能差些,一个不支持并发,性能好。但是 Go 官方为什么不提供两个,那就不得而知了,可能是为了符合 Go 语言“少就是多”的理念?

你有什么看法呢?一起聊聊……

相关文章
|
16天前
|
存储 JSON 监控
Viper,一个Go语言配置管理神器!
Viper 是一个功能强大的 Go 语言配置管理库,支持从多种来源读取配置,包括文件、环境变量、远程配置中心等。本文详细介绍了 Viper 的核心特性和使用方法,包括从本地 YAML 文件和 Consul 远程配置中心读取配置的示例。Viper 的多来源配置、动态配置和轻松集成特性使其成为管理复杂应用配置的理想选择。
35 2
|
14天前
|
Go 索引
go语言中的循环语句
【11月更文挑战第4天】
25 2
|
14天前
|
Go C++
go语言中的条件语句
【11月更文挑战第4天】
29 2
|
4天前
|
存储 Go
go语言 遍历映射(map)
go语言 遍历映射(map)
15 2
|
4天前
|
Go 调度 开发者
Go语言中的并发编程:深入理解goroutines和channels####
本文旨在探讨Go语言中并发编程的核心概念——goroutines和channels。通过分析它们的工作原理、使用场景以及最佳实践,帮助开发者更好地理解和运用这两种强大的工具来构建高效、可扩展的应用程序。文章还将涵盖一些常见的陷阱和解决方案,以确保在实际应用中能够避免潜在的问题。 ####
|
5天前
|
测试技术 Go 索引
go语言使用 range 关键字遍历
go语言使用 range 关键字遍历
14 3
|
5天前
|
测试技术 Go 索引
go语言通过 for 循环遍历
go语言通过 for 循环遍历
14 3
|
6天前
|
安全 Go 数据处理
Go语言中的并发编程:掌握goroutine和channel的艺术####
本文深入探讨了Go语言在并发编程领域的核心概念——goroutine与channel。不同于传统的单线程执行模式,Go通过轻量级的goroutine实现了高效的并发处理,而channel作为goroutines之间通信的桥梁,确保了数据传递的安全性与高效性。文章首先简述了goroutine的基本特性及其创建方法,随后详细解析了channel的类型、操作以及它们如何协同工作以构建健壮的并发应用。此外,还介绍了select语句在多路复用中的应用,以及如何利用WaitGroup等待一组goroutine完成。最后,通过一个实际案例展示了如何在Go中设计并实现一个简单的并发程序,旨在帮助读者理解并掌
|
5天前
|
Go 索引
go语言按字符(Rune)遍历
go语言按字符(Rune)遍历
19 3
|
9天前
|
Go API 数据库
Go 语言中常用的 ORM 框架,如 GORM、XORM 和 BeeORM,分析了它们的特点、优势及不足,并从功能特性、性能表现、易用性和社区活跃度等方面进行了比较,旨在帮助开发者根据项目需求选择合适的 ORM 框架。
本文介绍了 Go 语言中常用的 ORM 框架,如 GORM、XORM 和 BeeORM,分析了它们的特点、优势及不足,并从功能特性、性能表现、易用性和社区活跃度等方面进行了比较,旨在帮助开发者根据项目需求选择合适的 ORM 框架。
32 4