当前位置:首页 > 文章列表 > Golang > Go教程 > Go weak.Pointer 实战:缓存别越跑越胖,先搞懂弱引用和 AddCleanup

Go weak.Pointer 实战:缓存别越跑越胖,先搞懂弱引用和 AddCleanup

来源:Go Blog 2026-06-01 21:38:16 0浏览 收藏

如果你写过本地缓存、对象去重池、元数据索引,大概率遇到过这种尴尬:缓存是为了省资源,结果跑久了自己变成最大内存户。Go 1.24 加进来的 weak.Pointerruntime.AddCleanup,就是专门给这类高级场景准备的工具。但它不是银弹,用错了会比普通 map 更难排查。

这篇我按生产代码的视角来讲:弱引用到底弱在哪里、为什么 Value() 一定要判空、缓存清理怎么和 GC 配合,以及什么时候我宁愿不用它。

Go weak.Pointer 思维导图
思维导图:弱引用不是缓存框架,它只是给 GC 留出回收对象的空间。

先说人话:弱引用不是“更轻的指针”

普通指针会把对象“留住”。只要还有强引用指向这个对象,GC 就不能回收它。弱引用不一样,weak.Pointer 指向对象,但不会阻止 GC 回收对象。

这意味着一件很重要的事:你从弱指针里取值时,可能拿到对象,也可能拿到 nil。所以它的使用姿势不是“我存了就一定有”,而是“如果对象还活着,我就复用;如果对象已经没了,我就重新创建”。

它适合什么场景

我会优先想到三类场景:对象规范化、去重池、辅助缓存。比如同一个配置快照、同一个解析后的 schema、同一个大对象的共享表示,很多请求都可能用到,但业务上又不值得强行永久留在内存里。

如果你只是想做一个有 TTL、有容量、有淘汰策略的业务缓存,那弱引用不是首选。该用 ristretto、groupcache、bigcache 或者你自己的 LRU,就老老实实用缓存。弱引用解决的是“不要因为缓存 map 的引用让对象永远活着”这个问题。

Go weak.Pointer 缓存流程图
流程图:取弱引用、判空、创建对象、包装 weak.Pointer,再用 AddCleanup 做索引清理。

一个最小的弱引用缓存骨架

下面这段代码只是骨架,重点是看流程:先从 map 里拿弱指针,再 Value() 判空;对象不存在时重新创建,再用 weak.Make 放回去。

package cache

import (
    "sync"
    "weak"
)

type Schema struct {
    Name string
}

type Cache struct {
    mu sync.Mutex
    m  map[string]weak.Pointer[Schema]
}

func (c *Cache) Get(key string) *Schema {
    c.mu.Lock()
    defer c.mu.Unlock()

    if wp, ok := c.m[key]; ok {
        if v := wp.Value(); v != nil {
            return v
        }
    }

    v := &Schema{Name: key}
    c.m[key] = weak.Make(v)
    return v
}

这里最关键的就是这句:if v := wp.Value(); v != nil。弱引用里的对象可能已经被 GC 回收,取出来以后不能直接用。很多 bug 就来自“我以为缓存命中了,所以对象一定在”。

map 里的 key 谁来清理

弱引用还有一个现实问题:对象被 GC 回收了,但你的 map key 还在。值虽然取出来是 nil,但索引本身会越积越多。这个时候就轮到 runtime.AddCleanup 出场。

可以把它理解成对象被回收后的清理钩子。对象生命周期结束时,我们顺手把缓存里的 key 删除掉,避免 map 变成“空壳索引仓库”。

func (c *Cache) Get(key string) *Schema {
    c.mu.Lock()
    defer c.mu.Unlock()

    if wp, ok := c.m[key]; ok {
        if v := wp.Value(); v != nil {
            return v
        }
    }

    v := &Schema{Name: key}
    c.m[key] = weak.Make(v)

    runtime.AddCleanup(v, func(k string) {
        c.mu.Lock()
        delete(c.m, k)
        c.mu.Unlock()
    }, key)

    return v
}

这段代码表达的是思路,不建议你不加测试直接搬到生产。清理函数里能做什么、不能做什么,要结合官方文档认真看。我的习惯是让 cleanup 尽量短,只做删除索引、释放辅助资源这类动作,不在里面塞复杂业务逻辑。

Go weak.Pointer 代码案例图
代码案例:弱引用的三个重点,强引用会留住对象,弱引用不阻止 GC,Value 必须先判空。

它解决不了什么

弱引用不能替你设计缓存策略。它不知道热点 key 是什么,不知道哪些对象应该保留,也不知道什么时候应该主动淘汰。它只是告诉 GC:如果除了弱引用没人需要这个对象了,你可以回收。

所以它不适合拿来做强一致缓存,也不适合保存必须命中的业务数据。比如用户会话、权限规则、订单状态,这些东西不能因为 GC 心情好就突然没有。弱引用适合“能复用就复用,没了就重建”的派生对象。

生产里我会加哪些保护

  • 所有 Value() 后面都必须判空,review 时看到直接解引用我会拦下来。
  • 缓存构建逻辑必须可重入,因为弱引用失效后会重新创建对象。
  • map 访问仍然要加锁,weak.Pointer 不会让普通 map 变成并发安全。
  • cleanup 函数尽量短,不做网络调用、复杂日志和可能阻塞的操作。
  • 上线前看 heap、对象数量、GC 次数和缓存重建次数,不只看 QPS。

一个容易踩的坑:临时强引用会影响观察结果

你在测试 weak.Pointer 时,经常会发现“为什么 GC 后对象还在”。很多时候不是 weak 失效,而是你自己还拿着强引用,比如局部变量、闭包、slice、日志参数,甚至调试打印都可能延长对象生命。

写测试时最好把创建对象和触发 GC 的阶段隔开,避免无意间留住对象。否则你会误判 weak.Pointer 的行为,进而在生产代码里做出错误假设。

我的建议

weak.Pointerruntime.AddCleanup 是高级工具,不是日常业务 CRUD 的标配。它们适合内存敏感、对象可重建、缓存只是辅助优化的场景。用之前先确认:对象丢了能不能重建?缓存 miss 会不会影响正确性?cleanup 失败会不会影响主流程?

如果这些问题都能答清楚,它们会很有价值。尤其是那些“缓存引用把大对象一直挂住”的老问题,weak.Pointer 给了我们一个更像运行时原生能力的解法。我的态度是:可以用,但要带着指标、测试和 review 清单一起用。

参考资料:Go Blog《Cleanups and weak pointers》、Go 1.24 Release Notes。

版本声明
本文转载于:Go Blog 如有侵犯,请联系study_golang@163.com删除
Go 1.25 容器感知 GOMAXPROCS:K8s 里别再让 CPU limit 偷偷拖垮 P99Go 1.25 容器感知 GOMAXPROCS:K8s 里别再让 CPU limit 偷偷拖垮 P99
上一篇
Go 1.25 容器感知 GOMAXPROCS:K8s 里别再让 CPU limit 偷偷拖垮 P99
Go 迭代器实战:range over func 别写成炫技,先把 yield 用明白
下一篇
Go 迭代器实战:range over func 别写成炫技,先把 yield 用明白
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • ChatExcel酷表:告别Excel难题,北大团队AI助手助您轻松处理数据
    ChatExcel酷表
    ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
    5893次使用
  • Any绘本:开源免费AI绘本创作工具深度解析
    Any绘本
    探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
    6327次使用
  • 可赞AI:AI驱动办公可视化智能工具,一键高效生成文档图表脑图
    可赞AI
    可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
    6138次使用
  • 星月写作:AI网文创作神器,助力爆款小说速成
    星月写作
    星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
    8108次使用
  • MagicLight.ai:叙事驱动AI动画视频创作平台 | 高效生成专业级故事动画
    MagicLight
    MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
    6594次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码