当前位置: 首页 > news >正文

[翻译]从 unique 到 cleanups 和 weak:高效的新底层工具

Michael Knyszek
2025年3月6日

在去年的关于 unique 包的博文①中,我们提到了当时处于提案审查阶段的一些新特性。现在很高兴与大家分享,从 Go 1.24 开始,所有开发者都可以使用这些新特性了!这些新特性包括 runtime.AddCleanup 函数(用于在对象不可达时排队运行函数)和 weak.Pointer 类型(安全指向对象而不阻止其被垃圾回收)。这两大特性结合起来足以构建你自己的 unique 包!让我们深入探讨这些特性的实用场景和使用方法。

注意:这些新特性是垃圾回收器的高级功能。如果还不熟悉基本的垃圾回收概念,强烈建议先阅读垃圾回收指南的简介部分②。

Cleanups(清理函数)

如果使用过终结器(finalizer),那么对 cleanup 的概念应该不陌生。终结器是通过调用 runtime.SetFinalizer 与已分配对象关联的函数,在对象变得不可达后由垃圾回收器调用。从高层次看,cleanup 的工作原理类似。

我们通过一个使用内存映射文件的应用示例来说明 cleanup 的用法:

//go:build unixtype MemoryMappedFile struct {data []byte
}func NewMemoryMappedFile(filename string) (*MemoryMappedFile, error) {f, err := os.Open(filename)if err != nil {returnnil, err}defer f.Close()// 获取文件信息(需要文件大小)fi, err := f.Stat()if err != nil {returnnil, err}// 提取文件描述符conn, err := f.SyscallConn()if err != nil {returnnil, err}var data []byteconnErr := conn.Control(func(fd uintptr) {// 创建由该文件支持的内存映射data, err = syscall.Mmap(int(fd), 0, int(fi.Size()), syscall.PROT_READ, syscall.MAP_SHARED)})if connErr != nil {returnnil, connErr}if err != nil {returnnil, err}mf := &MemoryMappedFile{data: data}cleanup := func(data []byte) {syscall.Munmap(data) // 忽略错误}runtime.AddCleanup(mf, cleanup, data)return mf, nil
}

内存映射文件的内容直接映射到内存中。通过这段代码,当 *MemoryMappedFile 不再被引用时,内存映射会被自动清理。

注意 runtime.AddCleanup 的三个参数:

  1. 要附加 cleanup 的变量地址

  2. cleanup 函数本身

  3. 传给 cleanup 函数的参数

与 runtime.SetFinalizer 的关键区别在于:cleanup 函数的参数独立于附加对象。这个改变修复了终结器的若干问题。

终结器的痛点包括:

  • 涉及引用循环时会导致内存泄漏

  • 至少需要两次完整 GC 周期才能回收内存

  • 对象复活(resurrection)问题

cleanup 通过不传递原始对象解决了这些问题:

  1. 对象涉及循环引用仍可被回收

  2. 内存可以立即回收

弱指针(Weak Pointers)

假设我们需要通过文件名去重内存映射文件。使用 weak.Pointer 类型可以安全地实现缓存:

var cache sync.Map // map[string]weak.Pointer[MemoryMappedFile]func NewCachedMemoryMappedFile(filename string) (*MemoryMappedFile, error) {var newFile *MemoryMappedFilefor {// 尝试从缓存加载value, ok := cache.Load(filename)if !ok {// 创建新映射文件if newFile == nil {var err errornewFile, err = NewMemoryMappedFile(filename)if err != nil {returnnil, err}}// 尝试安装新映射文件wp := weak.Make(newFile)var loaded boolvalue, loaded = cache.LoadOrStore(filename, wp)if !loaded {runtime.AddCleanup(newFile, func(filename string) {cache.CompareAndDelete(filename, wp)}, filename)return newFile, nil}}// 检查缓存条目有效性if mf := value.(weak.Pointer[MemoryMappedFile]).Value(); mf != nil {return mf, nil}// 发现待清理的空条目cache.CompareAndDelete(filename, value)}
}

该示例展示的关键特性:

  • 弱指针可比较且具有稳定标识

  • 支持为单个对象添加多个独立 cleanup

  • 可实现通用缓存结构(见原文通用 Cache 结构示例)

注意事项与未来工作

使用这些特性时需注意:

  1. cleanup 关联对象不可被 cleanup 函数或其参数引用

  2. 弱指针作为 map 键时,值不能引用键对象

  3. 非确定性行为依赖 GC 实现细节

  4. 测试具有挑战性

未来可能改进方向:

  • Ephemeron(短命对象)支持

  • 直接追踪映射内存区域的 API

总结

runtime.AddCleanup 和 weak.Pointer 为 Go 带来了更精细的内存管理能力,但需要谨慎使用。大多数场景应通过标准库间接使用这些特性,而非直接操作。建议开发者仔细阅读更新后的垃圾回收指南③中的使用建议。

这些特性的加入体现了 Go 团队在保持语言简洁性的同时,也在为高级使用场景提供必要的底层支持。正如文中所说:"这些是带有微妙语义的高级工具",但正确使用它们可以解决一些原本难以处理的问题。

http://www.xdnf.cn/news/83089.html

相关文章:

  • 生产环境大数据平台权限管理
  • Neo4j 可观测性最佳实践
  • Vue指令详解:从入门到精通
  • BBR 的 minRTT 采集问题
  • 二叉树层序遍历技术解析与面试指南
  • 根据极点-零点分布进行状态空间模型降阶
  • 火山RTC 5 转推CDN 布局合成规则
  • 2024年ESWA SCI1区TOP:量子计算蜣螂算法QHDBO,深度解析+性能实测
  • 【简单学习】llamaindex环境搭建以及构建RAG
  • 真实趋势策略思路
  • 高并发秒杀使用RabbitMQ的优化思路
  • Vue2-重要知识点
  • Reflex 完全指南:用 Python 构建现代 Web 应用的终极体验
  • SpringCloud组件—Eureka
  • 面向组织的网络安全措施
  • 详解Node.js中的setImmediate()函数
  • 智慧城市新标配:苏州金龙无人清扫车开启城市清洁“智”时代
  • C++(初阶)(十二)——stack和queue
  • Web网页核心技术解析:从结构到节点操作
  • 大模型学习笔记------Llama 3模型架构之分组查询注意力(GQA)
  • [Git] Git Stash 命令详解
  • 【MATLAB第115期】基于MATLAB的多元时间序列的ARIMAX的预测模型
  • 线缆屏蔽与浪涌测试
  • Linux中查询进程服务,通过端口方式关闭
  • C++模板学习(进阶)
  • PH热榜 | 2025-04-22
  • 客户端本地搭建
  • Baidu Comate初体验:强大的全局ai工具
  • 嘻游后台系统与机器人模块结构详解:功能逻辑 + 定制改造实战
  • 性能比拼: Go vs Java