标签 包 下的文章

Go weak包前瞻:弱指针为内存管理带来新选择

本文永久链接 – https://tonybai.com/2024/09/23/go-weak-package-preview

在介绍Go 1.23引入的unique包的《Go unique包:突破字符串局限的通用值Interning技术实现》一文中,我们知道了unique包底层是基于internal/weak包实现的,internal/weak是一个弱指针功能的Go实现。所谓弱指针(Weak Pointer,也称为弱引用)是与强指针相对而言的,强指针(Strong Pointer,也可称作强引用)就是下面代码片段中的这种常规指针:

var p *T = new(T) // 假设T类型对象被分配到堆上

只要p指向堆上的T对象,那么T对象就无法被GC回收。但弱指针并非如此,它也可以指向堆上的某个内存对象(比如T类型对象),但它无法像强指针那样阻止GC回收该对象。

Go unique包的实现者Michael Knyszek近期提议在标准库引入weak包(实际上是将internal/weak公开暴露给Go开发者),该提议被Russ Cox代表的Go提案评审委员会所接受,最早将于Go 1.24版本落地。

在这篇短文中,我们来前瞻一下weak包的API设计、原理、应用场景以及社区对该提案一些观点。

注:weak包尚未落地,本文中的代码在Go 1.23中均无法运行,可以视作伪代码。

1. weak包的API

weak包的核心是Pointer[T]类型,它代表了对类型T的弱指针。以下目前Michael Knyszek为weak包设计的主要API:

type Pointer[T any] struct { ... }

func Make[T any](ptr *T) Pointer[T]

func (p Pointer[T]) Value() *T

Make函数用于创建一个弱指针,而Value方法则用于获取弱指针指向的实际值。如果原始对象已被垃圾回收,Value方法将返回nil。这个设计秉承了Go一贯的简洁,允许开发者轻松创建和使用弱指针,同时保持了Go语言的类型安全特性。

2. weak包弱指针的工作原理

在开篇时,我已经对弱指针的作用做了简单说明,这里结合上述weak包的API和提案中的设计原理再扩展一下。

弱指针的核心思想是允许引用内存而不阻止垃圾回收器回收它。垃圾回收器在回收对象时,会自动将所有指向该对象的弱指针设置为nil。这确保了弱指针不会产生悬空引用(dangling pointer)。

下图是weak包弱指针的工作原理示意图,展示了weak pointer的核心工作原理,包括间接对象的使用和垃圾回收时的行为:

简单看一下这张图:程序创建一个对象并通过weak.Make创建一个weak.Pointer(弱指针),在Go运行时内部,weak.Pointer通过8字节的间接对象引用原始对象。这个间接对象是weak.Pointer的内部字段,按当前internal/weak的实现来看,该字段是一个unsafe.Pointer。这个间接对象包含了实际的弱引用。

值得注意的是,弱指针的比较基于它们最初创建时使用的指针。即使原始对象被回收,两个由相同指针创建的弱指针仍然会被认为是相等的。这个特性使得弱指针可以安全地用作map的键。

3. weak包的典型使用场景

weak包的引入将为Go带来更灵活的内存管理机制,它允许开发者创建不会阻止垃圾回收的引用,从而在保持内存效率的同时,实现更复杂的数据结构和算法。特别是在处理缓存、规范化映射(Canonicalization mapping)等场景时。

以缓存为例,使用弱指针,我们可以创建不会阻止被缓存对象被垃圾回收的缓存系统,这对于管理内存敏感的大型缓存系统特别有用。下面提案中Russ Cox举的一个使用weak包实现简单缓存的示例(可理解为伪代码):

type Cache[K any, V any] struct {
    f func(*K) V
    m atomic.Map[uintptr, func() V]
}

func NewCache[K comparable, V any](f func(*K)V) *Cache[K, V] {
    return &Cache[K, V]{f: f}
}

func (c *Cache[K, V]) Get(k *K) V {
    kw := uintptr(unsafe.Pointer((k))
    vf, ok := c.m.Load(kw)
    if ok {
        return vf()
    }
    vf = sync.OnceValue(func() V { return c.f(k) })
    vf, loaded := c.m.LoadOrStore(kw, vf) // 原issue中似乎少了第二个参数vf
    if !loaded {
        // Stored kw→vf to c.m; add the cleanup.
        runtime.AddCleanup(k, c.cleanup, kw)
    }
    return vf()
}

func (c *Cache[K, V]) cleanup(kw uintptr) {
    c.m.Delete(kw)
}

var cached = NewCache(expensiveComputation)

这段代码定义了一个泛型缓存结构Cache,它有两个类型参数K和V,以及两个成员字段f和m:

  • f是一个函数,接受*K类型的指针,返回V类型的值,这是用于计算缓存值的函数。
  • m是一个原子映射,键是K类型的弱指针,值是返回V的函数。

NewCache是缓存的创建函数,接受一个计算函数f,返回初始化的Cache指针。

Cache类型的Get方法用于获取缓存的值,它首先创建键k的弱指针kw,然后以该弱指针为键尝试从缓存(atomicMap)中加载值。如果找到,直接返回缓存的值。如果未找到,使用sync.OnceValue创建一个只执行一次的函数,调用c.f(k)计算值。之后,尝试将新计算的函数存储到缓存中。 如果成功存储(即之前没有这个键),添加一个清理函数,最后返回计算后的Value值。

这个实现允许缓存中的键在不再被程序其他部分引用时被垃圾回收,从而避免了内存长期占用或是泄漏。

4. 社区声音

针对该weak包提案,Go社区的主要声音是支持的,认为weak包将为Go带来更灵活的内存管理机制,但也表示了对无法用好weak包这个低级机制的担忧,希望在正式文档或Go Tour中包含更多使用关于weak包的示例和最佳实践。

Go新版GC的主要设计者Richard L. Hudson提出了对sweeping storms和清理大型缓存中过时weak条目的担忧,并提出了使用ephemerons(一种更复杂的弱引用机制)的可能性,但也认识到其实现复杂度和性能开销较高。

也有一些Go社区开发者保持了对weak包的谨慎态度,比如fasthttp的维护者、VictorialMetrics的联创Aliaksandr Valialkin 就建议:在决定如何在Go中实现弱指针之前,最好先分析其他编程语言中弱指针的最常见的生产用例,并首先思考一下在标准库中为这些实际用例提供更高级别的解决方案而不是暴露较低级别的弱指针的方案是否会更好。

也有gopher提出:能否在提案中添加2-3个没有弱指针就无法解决的实际问题的例子,但Michael Knyszek并未回应。

5. 小结

weak包的引入让Go的工具箱更加完整,它为开发者提供了更细粒度的内存控制,同时其核心API也保持了Go简单易用的特性。

对于Go开发者来说,weak包使得某些复杂的内存管理场景变得更容易处理,但也需要开发者更好地理解垃圾回收机制和弱引用的工作原理。

社区对weak包的引入持积极态度,但也关注其实现细节、性能影响和最佳实践,同时也意识到了使用weak指针时可能面临的挑战。

不过,开发者在使用weak包时还是需要谨慎,毕竟过度使用弱指针可能会使代码变得难以理解和维护,最好的方法是将它用在最适合的场景下。


Gopher部落知识星球在2024年将继续致力于打造一个高品质的Go语言学习和交流平台。我们将继续提供优质的Go技术文章首发和阅读体验。同时,我们也会加强代码质量和最佳实践的分享,包括如何编写简洁、可读、可测试的Go代码。此外,我们还会加强星友之间的交流和互动。欢迎大家踊跃提问,分享心得,讨论技术。我会在第一时间进行解答和交流。我衷心希望Gopher部落可以成为大家学习、进步、交流的港湾。让我相聚在Gopher部落,享受coding的快乐! 欢迎大家踊跃加入!

img{512x368}
img{512x368}

img{512x368}
img{512x368}

著名云主机服务厂商DigitalOcean发布最新的主机计划,入门级Droplet配置升级为:1 core CPU、1G内存、25G高速SSD,价格5$/月。有使用DigitalOcean需求的朋友,可以打开这个链接地址:https://m.do.co/c/bff6eed92687 开启你的DO主机之路。

Gopher Daily(Gopher每日新闻) – https://gopherdaily.tonybai.com

我的联系方式:

  • 微博(暂不可用):https://weibo.com/bigwhite20xx
  • 微博2:https://weibo.com/u/6484441286
  • 博客:tonybai.com
  • github: https://github.com/bigwhite
  • Gopher Daily归档 – https://github.com/bigwhite/gopherdaily
  • Gopher Daily Feed订阅 – https://gopherdaily.tonybai.com/feed

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。

重拾精髓:go doc -http让离线包文档浏览更便捷

本文永久链接 – https://tonybai.com/2024/09/06/go-doc-add-http-support

Go语言团队近期接受了Go团队成员、Go圣经《The Go Programming Language》合著者Alan Donovan新提案,旨在进一步提升开发者体验。这个提案为go doc命令新增了一个强大的功能:通过go doc -http,开发者可以快速启动一个本地的文档服务器,并自动在浏览器中打开Go包的参考文档。该功能为开发者提供了类似pkg.go.dev的离线文档展示形式,同时增强了查看本地文档的交叉引用功能。看到这个提案功能,屏幕前的资深Gopher是不是感觉似曾相识呢:)。

早在去年,我就写过一篇有关go包文档查看方式对比的文章《聊聊godoc、go doc与pkgsite》,在那篇文章中,我就对当前Go包文档查看的几种方式做了详细说明,如果你是Go初学者,不妨点击链接移步过去仔细阅读一番。当然,这里也会简单地再介绍一下Go包文档离线查看工具的演进。

Go语言的包文档查看工具经历了三个重要阶段的演进,分别是godocgo docpkgsite。以下是这些工具的发展历程:

godoc是Go语言最早用于查看包文档的工具。它支持通过命令行查看文档,也可以通过-http参数启动一个本地文档服务器,用户在浏览器中以网页形式查看文档。这个工具提供了较为完整的Go包文档浏览体验,支持交叉引用和导航。但随着Go的发展,逐渐不再是官方推荐的工具,并且不再随Go安装包一并发布了!

随着Go的升级与演进,go doc逐渐取代了godoc成为查看包文档的主要工具。go doc主要提供了通过命令行输出包详细文档的能力,对应简单的包查询,这种方式更为高效:

$go doc -h
Usage of [go] doc:
    go doc
    go doc <pkg>
    go doc <sym>[.<methodOrField>]
    go doc [<pkg>.]<sym>[.<methodOrField>]
    go doc [<pkg>.][<sym>.]<methodOrField>
    go doc <pkg> <sym>[.<methodOrField>]
For more information run
    go help doc

Flags:
  -C dir
        change to dir before running command
  -all
        show all documentation for package
  -c    symbol matching honors case (paths not affected)
  -cmd
        show symbols with package docs even if package is a command
  -short
        one-line representation for each symbol
  -src
        show source code for symbol
  -u    show unexported symbols as well as exported

然而从上面的usage输出来看,go doc版本去除了godoc堪称精髓能力的-http支持,开发者无法像godoc那样启动本地文档服务器,这在某种程度上减少了它的可视化文档浏览功能。

pkgsite是目前官方推荐的在线Go包文档浏览工具,提供了一个全面、易于导航的网站(pkg.go.dev),用户可以在浏览器中查看各个Go包的文档、函数、类型等信息。它大大提升了开发者的体验,提供了丰富的交叉引用和包依赖信息。

pkgsite也是go官方站,主要用于在线查看,虽然也支持离线查看功能。但就像Alan Donovan在issue提到的那样:pkgsite程序目前相当大且启动缓慢,并且pkgsite最初被设计为一个可以在Google Cloud上运行的长生命周期的服务器,有很多外部依赖和耦合。

为了满足诸多Gopher通过浏览器web方式离线浏览Go包参考手册的需求,弥补pkgsite过于缓慢和庞大的不足,Alan Donovan提出了让离线文档服务能力回归的issue。没错!这个提案其实就是godoc -http这个经典的、精髓功能的“重生”。

这一新增功能有望在Go 1.24或之后的版本中正式推出,届时,新增的go doc -http功能会让离线文档服务的能力回归,为开发者提供了更多选择与灵活性。但目前go doc -http的具体命令接口形式尚未确定,但可以确定的是,通过该命令,用户无需再依赖第三方工具或访问外部网站,即可在本地查看项目的完整文档。这不仅提升了效率,也让开发者更方便地查找包文档以及包间的交叉引用,实现更直观的包依赖管理。Go开发者们可以尽情享受这一强大的本地文档浏览工具。


Gopher部落知识星球在2024年将继续致力于打造一个高品质的Go语言学习和交流平台。我们将继续提供优质的Go技术文章首发和阅读体验。同时,我们也会加强代码质量和最佳实践的分享,包括如何编写简洁、可读、可测试的Go代码。此外,我们还会加强星友之间的交流和互动。欢迎大家踊跃提问,分享心得,讨论技术。我会在第一时间进行解答和交流。我衷心希望Gopher部落可以成为大家学习、进步、交流的港湾。让我相聚在Gopher部落,享受coding的快乐! 欢迎大家踊跃加入!

img{512x368}
img{512x368}

img{512x368}
img{512x368}

著名云主机服务厂商DigitalOcean发布最新的主机计划,入门级Droplet配置升级为:1 core CPU、1G内存、25G高速SSD,价格5$/月。有使用DigitalOcean需求的朋友,可以打开这个链接地址:https://m.do.co/c/bff6eed92687 开启你的DO主机之路。

Gopher Daily(Gopher每日新闻) – https://gopherdaily.tonybai.com

我的联系方式:

  • 微博(暂不可用):https://weibo.com/bigwhite20xx
  • 微博2:https://weibo.com/u/6484441286
  • 博客:tonybai.com
  • github: https://github.com/bigwhite
  • Gopher Daily归档 – https://github.com/bigwhite/gopherdaily
  • Gopher Daily Feed订阅 – https://gopherdaily.tonybai.com/feed

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



Statcounter View My Stats