浅谈golang 中time.After释放的问题


Posted in Golang onMay 05, 2021

在谢大群里看到有同学在讨论time.After泄漏的问题,就算时间到了也不会释放,瞬间就惊呆了,忍不住做了试验,结果发现应该没有这么的恐怖的,是有泄漏的风险不过不算是泄漏,

先看API的说明:

// 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.
func After(d Duration) <-chan Time {
    return NewTimer(d).C
}

提到了一句The underlying Timer is not recovered by the garbage collector,这句挺吓人不会被GC回收,不过后面还有条件until the timer fires,说明fire后是会被回收的,所谓fire就是到时间了,

写个例子证明下压压惊:

package main
import "time"
func main() {
    for {
        <- time.After(10 * time.Nanosecond)
    }
}

显示内存稳定在5.3MB,CPU为161%,肯定被GC回收了的。

当然如果放在goroutine也是没有问题的,一样会回收:

package main
import "time"
func main() {
    for i := 0; i < 100; i++ {
        go func(){
            for {
                <- time.After(10 * time.Nanosecond)
            }
        }()
    }
    time.Sleep(1 * time.Hour)
}

只是资源消耗会多一点,CPU为422%,内存占用6.4MB。因此:

Remark: time.After(d)在d时间之后就会fire,然后被GC回收,不会造成资源泄漏的。

那么API所说的If efficieny is a concern, user NewTimer instead and call Timer.Stop是什么意思呢?这是因为一般time.After会在select中使用,如果另外的分支跑得更快,那么timer是不会立马释放的(到期后才会释放),

比如这种:

select {
    case time.After(3*time.Second):
        return errTimeout
    case packet := packetChannel:
        // process packet.
}

如果packet非常多,那么总是会走到下面的分支,上面的timer不会立刻释放而是在3秒后才能释放,

和下面代码一样:

package main
import "time"
func main() {
    for {
        select {
        case <-time.After(3 * time.Second):
        default:
        }
    }
}

这个时候,就相当于会堆积了3秒的timer没有释放而已,会不断的新建和释放timer,内存会稳定在2.8GB,

这个当然就不是最好的了,可以主动释放:

package main
import "time"
func main() {
    for {
        t := time.NewTimer(3*time.Second)
        select {
        case <- t.C:
        default:
            t.Stop()
        }
    }
}

这样就不会占用2.8GB内存了,只有5MB左右。因此,总结下这个After的说明:

1、GC肯定会回收time.After的,就在d之后就回收。一般情况下让系统自己回收就好了。

2、如果有效率问题,应该使用Timer在不需要时主动Stop。大部分时候都不用考虑这个问题的。

交作业。

补充:go语言基于time.After通道超时设计和通道关闭close

go语言中多个并发程序的数据同步是采用通道来传输,比如v:=<-chan,从通道里读取数据到v,是一个阻塞操作。可是如通道里没有数据写入,就是chan<-data,这样写入通道的操作,在读操作时就会一直阻塞,需要加入一个超时机制来进行判断。

具体的超时设计是通过使用select和case语句,类似于switch和case,在每一个case里进行一个io操作,比如读或者写,在最后一个case里调用time包里的After方法,可以达到超时检测效果。参考下面例子1

当然,如写入端在写入通道结束后,调用close(chan)关闭通道。在读取端,就会读到一个该通道类型的空值,如是int就是0,如是string就是""空字符串,可以根据这个空值来判断,或者使用两个返回值来读取通道:v,br:=<-chan,这里第2个参数br是一个bool变量,表示通道是否关闭。参考下面例子2

例子1如下:

package main 
import (
	"fmt"
	"time"
)
 
func main() {
	ch := make(chan string, 2)//定义了缓冲长度2的通道,类型是字符串,可以连续写入2次数据
	go func(c chan string) {
		for i := 0; i < 3; i++ {
			str := fmt.Sprintf("%d", i)
			c <- str
			time.Sleep(time.Millisecond * 10)
		}
	}(ch)
	go func(c chan string) {
		for i := 10; i < 13; i++ {
			str := fmt.Sprintf("%d", i)
			c <- str
			time.Sleep(time.Millisecond * 10)
		}
	}(ch)
	timelate := 0 //定义超时次数
	for {
		time.Sleep(time.Millisecond * 2000) //每隔2秒读取下管道
		select {
		case i := <-ch:
			fmt.Println("通道读取到:", i)
		case <-time.After(time.Second * 2): // 等待2秒超时,这里time.After 返回一个只读通道,就是当前时间值
			timelate++
			fmt.Printf("通道接收超时,第%d次\n", timelate)
			if timelate > 2 {
				goto end
			}
		}
	}
end:
	fmt.Println("退出88")
}

浅谈golang 中time.After释放的问题

例子2如下:

演示了close关闭通道,使用2个返回值来读取通道,获取通道关闭状态。

package main 
import (
	"fmt"
	"time"
)
 
func main() {
	ch := make(chan string, 2) //定义了缓冲长度2的通道,类型是字符串,可以连续写入2次数据
	go func(c chan string) {
		for i := 0; i < 3; i++ {
			str := fmt.Sprintf("%d", i)
			c <- str
			time.Sleep(time.Millisecond * 10)
		}
	}(ch)
	go func(c chan string) {
		for i := 10; i < 13; i++ {
			str := fmt.Sprintf("%d", i)
			c <- str
			time.Sleep(time.Millisecond * 10)
		}
		time.Sleep(time.Millisecond * 1000) //专门给这个协程加个1秒的延时,让它晚退出会,好调用close关闭通道。
		close(c)
	}(ch)
	timelate := 0 //定义超时次数
	for {
		time.Sleep(time.Millisecond * 2000) //每隔2秒读取下管道
		select {
		case i, br := <-ch: //从通道里读取2个返回值,第2个是通道是否关闭的bool变量
			if !br { //如果是false,表示通道关闭
				fmt.Println("通道关闭了")
				goto end
			}
			fmt.Println("通道读取到:", i)
		case <-time.After(time.Second * 2): // 等待2秒超时,这里time.After 返回一个只读通道,就是当前时间值
			timelate++
			fmt.Printf("通道接收超时,第%d次\n", timelate)
			if timelate > 2 {
				goto end
			}
		}
	}
end:
	fmt.Println("退出88")
}

浅谈golang 中time.After释放的问题

对于例子2来说,这里因为在通道写入端用close关闭通道了,所以case <-time.After这个方法的超时就不起作用了。这里暂且保留着吧。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持三水点靠木。如有错误或未考虑完全的地方,望不吝赐教。

Golang 相关文章推荐
为什么不建议在go项目中使用init()
Apr 12 Golang
Go语言切片前或中间插入项与内置copy()函数详解
Apr 27 Golang
go 原生http web 服务跨域restful api的写法介绍
Apr 27 Golang
Golang标准库syscall详解(什么是系统调用)
May 25 Golang
Go语言实现Snowflake雪花算法
Jun 08 Golang
深入理解go slice结构
Sep 15 Golang
Golang使用Panic与Recover进行错误捕获
Mar 22 Golang
Golang MatrixOne使用介绍和汇编语法
Apr 19 Golang
Golang map映射的用法
Apr 22 Golang
Golang 实现 WebSockets 之创建 WebSockets
Apr 24 Golang
Golang并发工具Singleflight
May 06 Golang
Go本地测试解耦任务拆解及沟通详解Go本地测试的思路沟通的重要性总结
Jun 21 Golang
golang 定时任务方面time.Sleep和time.Tick的优劣对比分析
May 05 #Golang
golang日志包logger的用法详解
May 05 #Golang
golang elasticsearch Client的使用详解
May 05 #Golang
goland设置颜色和字体的操作
golang协程池模拟实现群发邮件功能
golang 比较浮点数的大小方式
May 02 #Golang
解决Golang中goroutine执行速度的问题
May 02 #Golang
You might like
PHP输出数组中重名的元素的几种处理方法
2012/09/05 PHP
基于php导出到Excel或CSV的详解(附utf8、gbk 编码转换)
2013/06/25 PHP
Yii2 队列 shmilyzxt/yii2-queue 简单概述
2017/08/02 PHP
javascript 操作文件 实现方法小结
2009/07/02 Javascript
深入理解JavaScript系列(10) JavaScript核心(晋级高手必读篇)
2012/01/15 Javascript
JavaScript初学者应注意的七个细节小结
2012/01/30 Javascript
js 判断浏览器使用的语言示例代码
2014/03/22 Javascript
一个JS函数搞定网页标题(title)闪动效果
2014/05/13 Javascript
JS实现可展开折叠层的鼠标拖曳效果
2015/10/09 Javascript
基于javascript制作微信聊天面板
2020/08/09 Javascript
Bootstrap modal 多弹窗之叠加关闭阴影遮罩问题的解决方法
2017/02/27 Javascript
jQuery:unbind方法的使用详解
2017/08/14 jQuery
微信小程序使用audio组件播放音乐功能示例【附源码下载】
2017/12/08 Javascript
vue-cli3+ts+webpack实现多入口多出口功能
2019/05/30 Javascript
微信小程序全局变量改变监听的实现方法
2019/07/15 Javascript
jquery实现鼠标悬浮弹出气泡提示框
2020/12/23 jQuery
[01:10:58]Spirit vs NB Supermajor小组赛 A组败者组决赛 BO3 第二场 6.2
2018/06/03 DOTA
Python linecache.getline()读取文件中特定一行的脚本
2008/09/06 Python
本地文件上传到七牛云服务器示例(七牛云存储)
2014/01/11 Python
python中enumerate函数遍历元素用法分析
2016/03/11 Python
python机器学习案例教程——K最近邻算法的实现
2017/12/28 Python
Django 中使用流响应处理视频的方法
2018/07/20 Python
理想高通滤波实现Python opencv示例
2019/01/30 Python
python网络应用开发知识点浅析
2019/05/28 Python
pytorch自定义初始化权重的方法
2019/08/17 Python
python实现百度OCR图片识别过程解析
2020/01/17 Python
Python爬虫后获取重定向url的两种方法
2021/01/19 Python
玩具反斗城葡萄牙官方商城:Toys"R"Us葡萄牙
2016/10/21 全球购物
美国女士内衣在线折扣商店:One Hanes Place
2019/03/24 全球购物
咖啡店自主创业商业计划书
2014/01/22 职场文书
2014五年级班主任工作总结
2014/12/05 职场文书
2015年度个人业务工作总结
2015/04/27 职场文书
2015年统战工作总结
2015/05/19 职场文书
三八妇女节新闻稿
2015/07/17 职场文书
2016年三八红旗手先进事迹材料
2016/02/26 职场文书
创业计划书之网络外卖
2019/10/31 职场文书