Go sync.Map.LoadOrStore 的 loaded 为什么要单独判断
我在把一个“按 key 惰性创建对象”的缓存从互斥锁改成 sync.Map 时,第一次最容易混淆的并不是并发安全,而是 LoadOrStore 的两个返回值。它返回的 actual 不一定是本次传入的对象,loaded 也不是“调用是否成功”的标志。
判断当前 goroutine 是否真正把 value 放进了 Map,要看loaded == false;如果loaded == true,actual才是已经存在的旧值,当前传入的 value 没有成为结果。
官方文档地址:https://pkg.go.dev/sync#Map.LoadOrStore
先记住返回值:actual 是最终值,loaded 是来源
LoadOrStore(key, value) 做的是一次原子决策:key 已经存在时,返回已有值;key 不存在时,写入传入值并返回它。第一个返回值 actual 表示这次调用最终应当使用的值,第二个返回值 loaded 表示这个值是不是从 Map 中已经加载出来的。
package main
import (
"fmt"
"sync"
)
func main() {
var cache sync.Map
// 第一次调用没有 key,传入的对象会成为最终值。
actual, loaded := cache.LoadOrStore("user:42", "first")
fmt.Printf("actual=%v loaded=%v\n", actual, loaded)
// 第二次调用不会覆盖旧值,actual 是已有值,loaded 表示已命中。
actual, loaded = cache.LoadOrStore("user:42", "second")
fmt.Printf("actual=%v loaded=%v\n", actual, loaded)
}
这个例子的输出逻辑是:第一次得到 first false,第二次得到 first true。第二次传入的 second 只是候选值,并没有覆盖已经存在的内容。

沿着数据生命周期看三种结果
我后来不再把 LoadOrStore 记成“带默认值的 Load”,而是把一次调用拆成候选值、原子决策和最终值三个阶段,这样在并发场景里更不容易读错。
Map 中没有 key:候选值完成写入
当 key 不存在时,方法会把 value 存入 Map,并返回同一个逻辑结果。此时 loaded == false,它告诉调用方:这次调用完成了“占位”或“首次登记”。如果后续还要执行昂贵初始化,这个分支通常就是初始化责任的归属点。
Map 中已有 key:已有值覆盖候选值
当 key 已经存在时,方法不覆盖旧值,返回的 actual 是 Map 里的已有对象,loaded == true。因此不能写成“无论如何都使用本地创建的对象”,否则本地对象可能已经做过构造或打开了资源,却没有进入缓存。
多个 goroutine 同时到达:只有一个调用拿到 false
并发竞争时,多个 goroutine 都可以先准备候选值,但对同一个 key 的原子决策只有一个会走 loaded == false。其他调用会拿到同一个已存入的 actual,并看到 loaded == true。这也是它适合做一次性占位的原因。

把 loaded 接到惰性初始化流程
真正写业务代码时,我会先决定“创建对象是否便宜”。如果只是轻量配置,可以每个 goroutine 都创建候选值;如果创建动作会连接外部服务、打开文件或启动后台任务,就应该把昂贵动作放在只有赢家执行的路径中。
package cache
import (
"fmt"
"sync"
)
type Client struct {
Address string
}
var clients sync.Map
func GetClient(address string) (*Client, error) {
// 先构造候选值;这里只放轻量字段,不在这里建立外部连接。
candidate := &Client{Address: address}
actual, loaded := clients.LoadOrStore(address, candidate)
// loaded 为 true 时必须使用缓存中的 actual,而不是丢弃它。
client, ok := actual.(*Client)
if !ok {
return nil, fmt.Errorf("cache value for %q has unexpected type", address)
}
if loaded {
return client, nil
}
// 只有 loaded 为 false 的调用负责首次登记后的初始化动作。
// 如果初始化可能失败,应配合失败清理或单独的状态对象处理。
return client, nil
}
这里有一个很实际的取舍:LoadOrStore 能保证“哪个值进入 Map”的原子性,但不会自动回滚初始化失败,也不会把“正在初始化”和“初始化完成”区分开。如果初始化失败后不能保留半成品,就需要把状态字段、错误值或一个可等待的 promise 放进 Map,而不是只存一个裸指针。
几个容易把结果读反的边界
- 不要把 loaded 当成成功标志。 两条路径都可能正常返回,
loaded只说明最终值来自已有条目还是本次存入。 - 不要忽略 actual。 当
loaded == true时,真正应该交给调用方的是 actual,而不是本地候选值。 - nil 也要结合类型设计。 如果存入的是接口包装后的 nil 指针,Map 里仍可能存在这个 key;读取后再做类型断言和空指针判断。
- Delete 会开启下一轮竞争。 删除 key 后,下一次 LoadOrStore 可能再次出现
loaded == false,因此初始化代码必须允许生命周期重复发生。 - 它不是普通 map 的通用替代品。 官方文档建议只有在写入一次多次读取,或不同 key 被不同 goroutine 读写等场景中考虑 sync.Map;需要强类型和多个字段一起维护时,普通 map 配合锁通常更清楚。
我的结论:先问“谁拥有最终值”
现在遇到 LoadOrStore,我会先看两个问题:最终要使用哪个对象,以及这次调用是否拥有初始化责任。答案分别由 actual 和 loaded 给出:actual 是最终对象,loaded == false 才代表本次调用完成了首次存入。
如果只是做并发去重或缓存占位,这个 API 很顺手;如果还要处理初始化失败、重试、过期和关闭,就把这些状态显式建模,再让 Map 保存状态对象。这样既保留原子占位,也不会把“已写入”误认为“已完成初始化”。
相关问题
LoadOrStore 会覆盖已经存在的值吗?
不会。key 已存在时返回旧值并把 loaded 设为 true,传入值不会替换旧值。
为什么不能只调用 Load 再决定是否 Store?
因为两个调用之间存在竞争窗口,多个 goroutine 可能同时看到 key 不存在。LoadOrStore 把判断和写入合并成一次原子操作。
LoadOrStore 能保证初始化函数只执行一次吗?
不能直接保证。它只保证最终存入哪个值;如果候选值的构造本身很昂贵,应把初始化状态或一次性执行器作为值来管理。
Go http.Request.PathValue 为什么会返回空字符串
- 上一篇
- Go http.Request.PathValue 为什么会返回空字符串
- 下一篇
- Go gob 数据对应的结构体增加导出字段后还能不能继续解码
-
- Golang · Go教程 | 1小时前 | reflect · go ·
- Go reflect.FieldByName 同名嵌入字段返回无效值的排查方法
- 232浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Go gob 数据对应的结构体增加导出字段后还能不能继续解码
- 261浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Go http.Request.PathValue 为什么会返回空字符串
- 501浏览 收藏
-
- Golang · Go教程 | 1小时前 | 单元测试 · go · config Go rand 属性测试 MAXCOUNT quick.Check testing/quick
- Go testing/quick 怎么固定随机源并限制属性测试次数
- 256浏览 收藏
-
- Golang · Go教程 | 1小时前 | go · Go 文件权限 os.File.Chmod FileMode ModePerm
- Go os.File.Chmod 怎么只修改文件权限而保留类型位
- 338浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Go URL.ResolveReference 遇到基础路径末尾斜杠时为什么结果不同
- 157浏览 收藏
-
- Golang · Go教程 | 11小时前 | 命令行 · go · 工程实践 · go generate Go代码生成 go generate -run 生成指令筛选
- go generate -run 怎样只执行指定生成指令
- 453浏览 收藏
-
- Golang · Go教程 | 11小时前 | go · go test -json TestEvent Go测试事件 BuildEvent FailedBuild
- go test -json 输出怎样区分测试事件和构建事件
- 173浏览 收藏
-
- Golang · Go教程 | 11小时前 | 基准测试 · testing · 性能 · Go教程 · GOMAXPROCS RunParallel Go testing.B.SetParallelism 并行基准 IO吞吐
- Go testing.B.SetParallelism 提高 IO 型并行基准吞吐的配置方法
- 404浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 457次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 521次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 536次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 484次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 306次使用
-
- Go map 并发写 panic 怎么办:从共享 map 到可控写入路径
- 2026-06-30 123浏览
-
- SingleFlight模式的Go并发编程学习
- 2023-01-01 285浏览
-
- Go并发编程之sync.Once使用实例详解
- 2022-12-27 484浏览
-
- Go使用sync.Map来解决map的并发操作问题
- 2023-01-08 392浏览
-
- Go并发编程之goroutine使用正确方法
- 2022-12-31 241浏览

