Go 中 map 非线程安全,多 goroutine 读写会 panic;需用 make 或字面量初始化防 nil panic;遍历无序且禁止边遍历边删除;sync.Map 适用于多读少写场景,但类型不安全且功能受限。

Go 里的 map 不是线程安全的,直接在多 goroutine 中读写会触发 panic:"fatal error: concurrent map read and map write"。这是绝大多数新手踩的第一个坑,也是最该优先确认的点。
声明和初始化 map 要避免 nil 指针 panic
声明但未初始化的 map 是 nil,对它做 delete 或赋值不会报错,但读取(如 v, ok := m["key"])虽能运行,遍历时却会 panic。更隐蔽的是,向 nil map 写入会直接 panic。
- 正确方式是用
make显式初始化:userMap := make(map[string]int)
- 或用字面量初始化:
config := map[string]string{"host": "localhost", "port": "8080"} - 判断是否为
nil可用m == nil,但更常见的是直接检查 key 是否存在:if val, ok := userMap["id"]; ok { /* 使用 val */ }
遍历 map 时无法保证顺序,且迭代中不能删元素
Go 的 map 迭代顺序是随机的(从 Go 1.0 起就刻意如此),每次运行结果都可能不同。这不是 bug,是设计选择——防止程序依赖隐式顺序。
- 需要固定顺序(比如按 key 排序输出),得先收集 key 到 slice,再排序:
keys := make([]string, 0, len(m)) for k := range m { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { fmt.Println(k, m[k]) } - 遍历时禁止调用
delete(m, k),否则行为未定义(可能跳过元素、重复遍历,甚至 panic)。要删元素,先收集待删 key,循环结束后再删。
用 sync.Map 替代普通 map 实现并发安全
标准库 sync.Map 是专为“多读少写”场景优化的并发安全 map,但它不是通用替代品:不支持 len()、不能直接 range 遍历、API 更冗长,且底层用分片锁 + read/write map 双结构实现。
立即学习“go语言免费学习笔记(深入)”;
- 适用场景:
cache类逻辑(如请求 ID → 处理状态)、配置热更新等;不适用于 高频写入或需要遍历全部键值的场景。 - 基本用法:
var m sync.Map m.Store("user_123", &User{ID: 123}) if val, ok := m.Load("user_123"); ok { u := val.(*User) } m.Delete("user_123") - 注意:
sync.Map的Load/Store接口参数类型是interface{},没有类型安全,容易因类型断言失败 panic,务必校验ok。
真正麻烦的从来不是语法怎么写,而是搞清「这个 map 会被谁读、谁写、频率如何、是否跨 goroutine」——没想清楚这点,用 sync.Map 可能比加 sync.RWMutex 更慢,而裸用普通 map 又必然崩溃。










