Go1.23 新特性:花了近 10 年,time.After 终于不泄漏了!

开发 前端
今天给大家分享了一个花了将近 10 年,Go 才解决的计时器泄露问题。为此还是要给 rsc 点赞的,至少一直都有记着。就是这个解决速度比较慢,很多人在真实的 Go 工程中都已经遇到过了。

大家好,我是煎鱼。

好多年前,我写过 timer.After 的使用和坑。Go 这么多年以来这块一直有内存泄露。有的同学或多或少都有遇到过。

最近 Go1.23 即将正式发布,Go 核心团队负责人 rsc 自述花了将近 10 年的努力,终于把这个问题修复了。值得我们关注!

timer.After 是什么

这是之前编写的部分,我测试验证了下。在 Go1.22 依然有效,仍然是有问题的。因此没有做什么修改。主要是给大家做知识温习回顾的作用。

今天是男主角是 Go 标准库 time 所提供的 After 方法。函数签名如下:

func After(d Duration) <-chan Time

该方法可以在一定时间(根据所传入的 Duration)后主动返回 time.Time 类型的 channel 消息。

在常见的场景下,我们会基于此方法做一些计时器相关的功能开发,例子如下:

func main() {
    ch := make(chan string)
    go func() {
        time.Sleep(time.Second * 3)
        ch <- "脑子进煎鱼了"
    }()

    select {
    case _ = <-ch:
    case <-time.After(time.Second * 1):
        fmt.Println("煎鱼出去了,超时了!!!")
    }
}

在运行 1 秒钟后,输出结果:

煎鱼出去了,超时了!!!

上述程序在在运行 1 秒钟后将触发 time.After 方法的定时消息返回,输出了超时的结果。

有什么问题和坑

从例子来看似乎非常正常,也没什么 “坑” 的样子。莫非是虚晃一枪?

我们再看一个不像是有问题例子,这在 Go 工程中经常能看见,只是大家都没怎么关注。

代码如下:

func main() {
    ch := make(chan int, 10)
    go func() {
        in := 1
        for {
            in++
            ch <- in
        }
    }()

    for {
        select {
        case _ = <-ch:
            // 煎鱼干了点什么...
            continue
        case <-time.After(3 * time.Minute):
            fmt.Printf("现在是:%d,我脑子进煎鱼了!", time.Now().Unix())
        }
    }
}

在上述代码中,我们构造了一个 for+select+channel 的一个经典的处理模式。

同时在 select+case 中调用了 time.After 方法做超时控制,避免在 channel 等待时阻塞过久,引发其他问题。

看上去都没什么问题,但是细心一看。在运行了一段时间后,我的笔记本电脑已经温热了许多。

粗暴的利用 top 命令一看:

图片图片

例子中 Go 工程的内存占用竟然已经达到了 30+GB 之高,并且还在持续增长。在再等待了一段时间后(所设置的超时时间到达),Go 工程的内存占用也没有要恢复合理的数值。这非常可怕。

这明显就是存在内存泄露的问题。

问题原因

这个内存泄露的问题,无容置疑是 Go 官方认可的 BUG。

快速的用一句话来讲,核心原因在于:for select 已结束,无法被 GC,时间堆内的被触发的计时器还在。

Go 官方文档说明Go 官方文档说明

如果是想深入看原因可以查看以前我写的《Go 内存泄露之痛,这篇把 Go timer.After 问题根因讲透了!》

Go1.23 timer.After 不泄露了!

在现在 2024 年,经过将近十年的努力,Go 核心团队负责人 rsc 终于解决了这个问题!!!

图片图片

自 Go1.23 版本起,会对用于计时器的通道(或者可能是用于通道的计时器)进行特殊处理,以便当没有通道操作待处理时,计时器将不会存放在计时器堆中。

这意味着当一旦不再引用通道和计时器,就可以对其进行 GC,不必等待计时器到期或明确停止计时器。

注:这里的计时器是指 time.After、time.NewTimer 和 time.NewTicker 使用的数据结构。

测试和验证

可能会有的同学会想体验 Go1.23 的新特性,验证这个 time.After 的修复是否有效。要特别注意下面这一点。

我们还是用前面提到的问题代码来测试。但如果你直接在本地复用,可能不一定能生效,会看到还是有内存泄露的情况。

主要是两个原因,如下:

1、你要下载 Go 新版本并使用 Go1.23 运行:

// 安装 go1.23rc2 的 go 新版本
$ go install golang.org/dl/go1.23rc2@latest
$ go1.23rc2 download

// 运行煎鱼前面的代码例子
$ go1.23rc2 run main.go

2、项目的 go.mod 文件注意 go 版本在 1.23,否则该新特性将由于兼容性保障无法生效:

图片图片

运行一段时间后,之前的代码中 Go1.23rc2 下内存情况基本正常:

图片图片

总结

今天给大家分享了一个花了将近 10 年,Go 才解决的计时器泄露问题。为此还是要给 rsc 点赞的,至少一直都有记着。就是这个解决速度比较慢,很多人在真实的 Go 工程中都已经遇到过了。

另外从新版本开始,大家在旧项目体验新特性是,要注意项目 go.mod 的 go 行版本或是 go toolchain 版本,避免由于版本过低而无法测试到真实的新特性效果。

责任编辑:武晓燕 来源: 脑子进煎鱼了
相关推荐

2024-08-07 08:51:20

Go优化开发

2024-09-09 08:56:03

2024-08-20 08:51:41

2020-09-22 07:49:05

内存泄漏

2024-09-02 00:30:41

Go语言场景

2021-08-30 10:49:39

Go语言编译器

2021-09-05 18:25:30

Go命令仓库

2018-10-31 12:41:11

2021-12-09 08:50:35

Kubernetes增强功能版本更新

2024-01-22 00:30:00

Go编程Go 1.22

2017-12-18 17:21:56

AndroidJava内存泄漏

2024-09-02 10:21:21

2020-10-10 09:01:54

泄漏

2019-03-05 15:03:09

Android Q安卓系统功能

2010-07-20 10:19:06

Wine 1.2

2022-02-11 21:01:18

GoNetip网络库

2021-02-02 09:10:12

Go语言二进制

2022-06-30 06:00:30

Edge浏览器

2023-12-11 09:02:27

CSS前端CSS 新特性

2015-07-06 09:46:21

AeroGearAndroid推送
点赞
收藏

51CTO技术栈公众号