[编程技术] Golang time.After内存泄漏分析
作者:CC下载站 日期:2022-05-06 00:00:51 浏览:6 分类:编程开发
背景
我刚转做go语言开发开始写入职小程序的时候,写下了如下的代码:
1
2
3
4
5
6
7
8
9
10
for {
select {
case conn := <- conns:
... //do someting
return conn
case <- time.After(c.Timeouot):
... //do something
return xxx
}
}
这只是一个简单的从连接池取连接
的代码,如果连接池没有可用连接,就在超时后创建一个新的连接使用。
说实话,我觉得我的代码可优雅了,后来对go语言有了更深入的了解之后,发现我写的代码有着明显的内存泄漏的问题。
熟悉go语言的朋友可能已经看出来了,内存泄漏的代码就是time.After
。
这是go语言经典的坑
,或者说是对go语言机制不了解的初学者经常会踩的坑,稍不留神就会掉到坑里去。
今天我们就来分析一下这个time.After
内存泄漏之谜。
我们来用最简单的例子模拟一下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main
import (
"fmt"
"time"
)
//define a channel
var chs chan int
func Get() {
for {
select {
case v := <- chs:
fmt.Printf("print:%v\n", v)
case <- time.After(3 * time.Minute):
fmt.Printf("time.After:%v", time.Now().Unix())
}
}
}
func Put() {
var i = 0
for {
i++
chs <- i
}
}
func main() {
chs = make(chan int, 100)
go Put()
Get()
}
咱也别管代码优不优雅,就很简单的逻辑,先往channel里面存数据,然后不停的使用for select case
语法从channel里面取数据。
我相信大家一眼就能看懂。就是这么简单的代码却会导致内存泄漏。
发现我们的进程MEM
这一列的值迅速增加,不到一分钟就占用了6G的内存,明显产生了内存泄漏。
原理分析
要了解为什么会产生内存泄漏,我们需要看看time package
里面time.After
函数的的定义。https://pkg.go.dev/time
func After(d Duration) <-chan Time
After waits for the duration to elapse and then sends the current time on the returned channel. It is equivalent to NewTimer(d).C. The underlying Timer is not recovered by the garbage collector until the timer fires. If efficiency is a concern, use NewTimer instead and call Timer.Stop if the timer is no longer needed.
该方法可以在一定时间(根据所传入的 Duration)后主动返回 time.Time 类型的 channel 消息。
注意: 描述里面写的很清楚:
在计时器触发之前,垃圾收集器不会回收Timer
如果考虑效率,需要使用NewTimer替代
1
2
3
4
5
6
7
8
for {
select {
case v := <- chs:
fmt.Printf("print:%v\n", v)
case <- time.After(3 * time.Minute):
fmt.Printf("time.After:%v", time.Now().Unix())
}
}
回过头来看我们的代码,这里我们的定时时间设置的是3分钟, 在for循环内每次select的时候,都会实例化一个一个新的定时器。该定时器在3分钟后,才会被激活,但是激活后已经跟select无引用关系,被gc给清理掉。
重点是: 在select里面虽然我们没有执行到time.After,但是这个对象已经初始化了,依然在时间堆里面,定时任务未到期之前,是不会被gc清理的。
解决方案
根据time.After
的解释,我们知道,for select case
里面最好不要用time.After
,go语言对这个方法的设计决定了它不能这么用,否则会有内存泄漏的风险。
至于解决方案,time package
的文档也说明了,使用NewTimer
来做定时器,不需要每次都创建定时器对象。
time.After
虽然调用的是timer定时器,但是他没有使用time.Reset()
方法再次激活定时器,所以每一次都是新创建的实例,才会造成的内存泄漏。
而我们使用NewTimer
创建定时器,再加上time.Reset
每次重新激活定时器,即可完美解决问题。
1
func NewTimer(d Duration) *Timer
NewTimer creates a new Timer that will send the current time on its channel after at least duration d.
而Timer
的指针里面封装了:
1
2
3
type Timer struct {
C <-chan Time
}
The Timer type represents a single event. When the Timer expires, the current time will be sent on C, unless the Timer was created by AfterFunc. A Timer must be created with NewTimer or AfterFunc.
我们改造一下上面的Get函数,使用time.NewTimer
来初始化Timer
的指针,并用Reset
来重置定时器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func Get() {
delay := time.NewTimer(3 * time.Minute)
defer delay.Stop()
for {
delay.Reset(3 * time.Minute)
select {
case v := <- chs:
fmt.Printf("print:%v\n", v)
case <- delay.C:
fmt.Printf("time.After:%v", time.Now().Unix())
}
}
}
记得一定要使用Reset重置定时器,如果不重置,那么定时器还是从创建的时候开始计算时间流逝。使用了Reset之后,每次都从当前Reset的时间开始算。
这么改之后,再次测试代码,发现内存一直稳定。
总结
在for select case
里面使用定时器一定要小心,定时器只有等到计时器触发之后才会被垃圾回收器回收,而time.After
是单次触发且无法Reset。
这种需要循环内触发定时器的用例,还是要使用time.NewTimer
并手动Reset
。
<全文完>
猜你还喜欢
- 03-29 [编程相关] Winform窗体圆角以及描边完美解决方案
- 03-29 [前端问题] has been blocked by CORS policy跨域问题解决
- 03-29 [编程相关] GitHub Actions 入门教程
- 03-29 [编程探讨] CSS Grid 网格布局教程
- 10-12 [编程相关] python实现文件夹所有文件编码从GBK转为UTF8
- 10-11 [编程算法] opencv之霍夫变换:圆
- 10-11 [编程算法] OpenCV Camshift算法+目标跟踪源码
- 10-11 [Python] python 创建 Telnet 客户端
- 10-11 [编程相关] Python 基于 Yolov8 + CPU 实现物体检测
- 03-15 [脚本工具] 使用go语言开发自动化脚本 - 一键定场、抢购、预约、捡漏
- 01-08 [编程技术] 秒杀面试官系列 - Redis zset底层是怎么实现的
- 01-05 [编程技术] 《Redis设计与实现》pdf
取消回复欢迎 你 发表评论:
- 精品推荐!
-
- 最新文章
- 热门文章
- 热评文章
[趣味工具] 【桌面动态小装扮】蝴蝶1.0、蟑螂1.2、苍蝇1.3
[辅助工具] 《小说角色更名器》+《小说广告清理器》V2.0.1
[Android] 人体穴位图解 v3.1.3(可离线)支持Android + ios
[辅助工具] 微信朋友圈导出工具 WechatMoments v0.0.1便携版
[AI工具] RWKV Runner-开箱即用的AI软件-聊天 写作 作曲一键完成
[Android] 小书包app开源小说v24.04.14内置书源版
[图像制作] StampDesign-印章设计制作软件
[美剧] 《辐射》全集
[课程] 2024小红书变现课:小红书无货源电商课
[游戏娱乐] 《漫威蜘蛛侠2》v1.4.0非官方移植PC版
[资料] [大学期末救急课] 猴博士+高斯课堂+斐多课堂,全集视频合集
[云资源] 价值2万元的老男孩Python教程
[书库] 史上最全摄影书推荐(附700本PDF版打包下载)
[云资源] 花了一千多元买的私人健身教程
[下载工具] Internet Download Manager 6.42.7 (IDM)
[影视] 灌篮高手 WEB-DL版下载/Slam Dunk/スラムダンク/灌篮高手:THE FIRST/灌篮高手电影版 2022 The First Slam Dunk 61.35G
[资料] 3000 套电影电视剧 LOGO 宣传片常用音效合集包
[安卓软件] 酷我音乐APP_v10.7.6.4 去广告破解豪华VIP版
[即时通讯] 微信PC版WeChat 3.9.9.43 多开防撤回绿色版
[安卓软件] Solid Explorer文件管理器APP 2.8.38 破解版
[云资源] 价值2万元的老男孩Python教程
[影视] 灌篮高手 WEB-DL版下载/Slam Dunk/スラムダンク/灌篮高手:THE FIRST/灌篮高手电影版 2022 The First Slam Dunk 61.35G
[云资源] 花了一千多元买的私人健身教程
[书库] 史上最全摄影书推荐(附700本PDF版打包下载)
[动画] 北斗神拳(1984) [两季合集] [MKV]
[资料] 抗战阵亡将士资料+续编
[电视剧] 三体 (2024) 全8集 网飞版本 中文字幕 合集
[纪录片] 河西走廊【10集 国语 中文字幕 1080P 10.8G MP4】
[电影] 2024年喜剧片·热辣滚烫 [mp4]
[影视] 铁爪 WEB-DL版下载 2023 The Iron Claw 23.48G
- 最新评论
-
我想看看mw2ddyy 评论于:04-26 好东西阿zfy123123 评论于:04-18 谢谢楼主xiaoqi 评论于:04-12 勿在线解压,勿手机解压,请在电脑上用最新款压缩软件解压!推荐360压缩或者好压CC下载站 评论于:04-10 无法解压啊,客服能不能给个解压教程ravengrey 评论于:04-10 谢谢支持!!CC下载站 评论于:03-26 很棒的资源,感谢分享云体风身 评论于:03-26 感谢分享,好东西云体风身 评论于:03-26 谢谢支持!CC下载站 评论于:03-14 央视精品,感谢付出提供。qwer9009 评论于:03-14
- 热门tag