golang channel 未关闭导致的内存泄漏
现象
某一个周末我们的服务 oom了,一个比较重要的job 没有跑完,需要重跑,以为是偶然,重跑成功,因为是周末没有去定位原因
又一个工作日,它又oom了,重跑成功,持续观察,job 在oom之前竟然占用了30g左右(这里我们的任务一个数据量都在内存中计算,所以这里数据量大一点)
应用使用30g肯定是不正常的,怀疑内存泄漏了,怎么定位内存泄漏呢?
定位
搜了一下网上经常用到的工具是 go 的 pprof 火焰图,自己在本地跑了一下,因为数据量比较少,并没有发现什么,暂时放下了。
后续某个早上在公司工具里面打开了一下,发现有火焰图的工具,打开看了一下一个函数占用了 7224.46mb,占用了 7个g, 而且这个函数是已经跑完了,这个时候定位到那个函数了,和旁边同事说了一下,同事帮忙看了下邮件告警,每个下午都会有任务失败告警(任务失败会进行重试的); 这里怀疑是失败了, channel 没有关闭,导致 消费的go routine 没有回收。
举个例子看下代码:
package main
import (
"context"
"fmt"
"golang.org/x/sync/errgroup"
)
func main() {
readGroup, _ := errgroup.WithContext(context.Background())
consumeGroup, _ := errgroup.WithContext(context.Background())
var (
data = make(chan []int, 10)
)
// 3个生产者往里面进行进行生产
readGroup.Go(func() error {
for i := 0; i < 3; i++ {
data <- []int{i}
}
return nil
})
readGroup.Go(func() error {
for i := 3; i < 6; i++ {
data <- []int{i}
}
return nil
})
readGroup.Go(func() (err error) {
for i := 6; i < 9; i++ {
// error
if i == 7 {
err = fmt.Errorf("error le")
return
}
data <- []int{i}
}
return nil
})
// 其中一个生产者遇到error 返回导致 channel 没有关闭,消费者没有退出
// 1个消费者进行消费
consumeGroup.Go(func() error {
for i := range data {
fmt.Println(i)
}
return nil
})
if err := readGroup.Wait(); err != nil {
fmt.Println(err)
return
}
close(data)
if err := consumeGroup.Wait(); err != nil {
fmt.Println(err)
return
}
fmt.Println("end it")
}
这个case里面,readGroup 遇到error 直接退出了,channel并没有关闭,如果是常驻进程的程序,消费的go routine 并没有回收,就导致了内存泄漏
最简单的关闭修复
将 close 放到最上面的 defer close(data)
不过最好的还是生产者进行关闭,我们可以优化一下代码,把生产者的代码放到一个函数中,这样就可以让生产者去进行关闭的操作了
package main
import (
"context"
"fmt"
"golang.org/x/sync/errgroup"
)
func main() {
var (
data = make(chan []int, 10)
err error
eg, _ = errgroup.WithContext(context.Background())
)
eg.Go(func() (err error) {
defer close(data)
err = readGroup(data)
return
})
eg.Go(func() (err error) {
err = consumeGroup(data)
return
})
err = eg.Wait()
if err != nil {
return
}
fmt.Println("end it")
}
func consumeGroup(data chan []int) (err error) {
consumeGroup, _ := errgroup.WithContext(context.Background())
consumeGroup.Go(func() error {
for i := range data {
fmt.Println(i)
}
return nil
})
if err = consumeGroup.Wait(); err != nil {
fmt.Println(err)
return
}
return
}
func readGroup(data chan []int) (err error) {
readGroup, _ := errgroup.WithContext(context.Background())
// 3个生产者往里面进行进行生产
readGroup.Go(func() error {
for i := 0; i < 3; i++ {
data <- []int{i}
}
return nil
})
readGroup.Go(func() error {
for i := 3; i < 6; i++ {
data <- []int{i}
}
return nil
})
readGroup.Go(func() (err error) {
for i := 6; i < 9; i++ {
// error
if i == 7 {
err = fmt.Errorf("error le")
return
}
data <- []int{i}
}
return nil
})
if err = readGroup.Wait(); err != nil {
fmt.Println(err)
return
}
return
}
修复
将生产者放在一个 goroutint 里面,最后如果遇到error的话 defer()的时候会把channel给关闭了
The Channel Closing Principle
One general principle of using Go channels is don't close a channel from the receiver side and don't close a channel if the channel has multiple concurrent senders. In other words, we should only close a channel in a sender goroutine if the sender is the only sender of the channel.
简单点:就是在生产者中进行channel的关闭
后续讨论和遇到的新问题
拆分代码函数的时候又遇到新的问题了,又一个切片数组我拆分函数的时候,我没有去接受切片函数的返回值,导致了切片发生扩容返回的是一个空切片,并没有修改掉原来的切片。之前以为在golang里面切片是引用类型,会自动改变其中的值最后查了一下,在go 里面都是值传递,可以修改其中的值其实是使用了指针修改了同一块地址中的值所以值发生了变化
总结
使用channel 的时候在生产者中进行关闭,思考一些遇到error的时候channel是否可以正常的关闭
go 中只有值传递,引用传递是修改了同一个指向内存地址中的值
参考文章:
如何优雅地关闭Go channel
Go语言参数传递是传值还是传引用
golang channel 未关闭导致的内存泄漏的更多相关文章
- but has failed to stop it. This is very likely to create a memory leak(c3p0在Spring管理中,连接未关闭导致的内存溢出)
以下是错误日志信息: 严重: The web application [/news] registered the JDBC driver [com.mysql.jdbc.Driver] but fa ...
- 在Activity中使用Thread导致的内存泄漏
https://github.com/bboyfeiyu/android-tech-frontier/tree/master/issue-7/%E5%9C%A8Activity%E4%B8%AD%E4 ...
- static关键字所导致的内存泄漏问题
大家都知道内存泄漏和内存溢出是不一样的,内存泄漏所导致的越来越多的内存得不到回收的失手,最终就有可能导致内存溢出,下面说一下使用staitc属性所导致的内存泄漏的问题. 在dalvik虚拟机中,sta ...
- vue自定义指令导致的内存泄漏问题解决
vue的自定义指令是一个比较容易引起内存泄漏的地方,原因就在于指令通常给元素绑定了事件,但是如果忘记了解绑,就会产生内存泄漏的问题. 看下面代码: directives: { scroll: { in ...
- 使用block的时候,导致的内存泄漏
明确,只要在block里边用到我们自己的东西,成员变量,self之类的,我们都需要将其拿出来,把它做成弱指针以便之后进行释放. 在ZPShareViewController这个控制器中,由如下代码: ...
- 精华阅读第 13 期 |常见的八种导致 APP 内存泄漏的问题
本期是移动开发精英俱乐部的第13期文章,都是以技术为主,所以这里就不过多的进行赘述了,我们直接看干货内容吧!本文系ITOM管理平台OneAPM整理. 实际项目中的MVVM(积木)模式–序章 导读:开篇 ...
- 一个驱动导致的内存泄漏问题的分析过程(meminfo->pmap->slabtop->alloc_calls)
关键词:sqllite.meminfo.slabinfo.alloc_calls.nand.SUnreclaim等等. 下面记录一个由于驱动导致的内存泄漏问题分析过程. 首先介绍问题背景,在一款嵌入式 ...
- 使用HandyJSON导致的内存泄漏问题相关解决方法
在移动开发中,与服务器打交道是不可避免的,从服务器拿到的接口数据最终都会被我们解析成模型,现在比较常见的数据传输格式是json格式,对json格式的解析可以使用原生的解析方式,也可以使用第三方的,我们 ...
- iOS - Block产生Memory Leaks循环引用导致的内存泄漏以及解决方案
在ARC(自动引用技术)前,Objective-c都是手动来分配释放 释放 计数内存,其过程非常复杂. ARC技术推出后,貌似世界和平了很多,但是其实ARC并不等同于Java或者C#中的垃圾回收,AR ...
- android Context 持有导致的内存泄漏
Context使用场景 为了防止Activity,Service等这样的Context泄漏于一些生命周期更长的对象,可以使用生命周期更长的ApplicationContext,但是不是所有的Conte ...
随机推荐
- Claude:除ChatGPT外的另一种选择
前言 Claude 是 Anthropic 开发的人工智能产品.Anthropic 是由 11 名前 OpenAI 员工于 2022 年创立的人工智能公司,旨在构建安全.可解释和有益于人类的人工智能系 ...
- 性能_2 Jmeter脚本增强
一.写脚本注意事项(回顾): 协议: http,https必须写 域名或ip: 不能有/ 请求方法: 看清楚接口文档 路径: 不要把 域名和ip再次 路径中,前后空格要看清楚 %20 空格的urlen ...
- TextArea设置MaxLength的代码(未测试在不同浏览器下的兼容性)
function SetTextAreaMaxLength(controlId,length) { // JScript File for TextArea // Keep user from ent ...
- 查找命令 (which 、 find )----grep 、 wc 和管道符,echo ,反引号 `
which命令 通过which命令,查看所使用的一系列命令的程序文件存放在哪里 find命令 按文件大小查找文件 语法:find 起始路径 -size [(+,-)k,m,g ] •+.-表示 ...
- 2021-01-09:linux中,某一个实时日志通过什么命令查?
福哥答案2020-01-09:[答案来自此链接:](https://www.zhihu.com/question/438536200)1.tailtail -f首先就是 tail -f,tail 命令 ...
- 2021-04-04:给定一个非负数组arr,和一个正数m。 返回arr的所有子序列中累加和%m之后的最大值。
2021-04-04:给定一个非负数组arr,和一个正数m. 返回arr的所有子序列中累加和%m之后的最大值. 福大大 答案2021-04-04: 自然智慧即可. 1.递归,累加和. 2.动态规划,累 ...
- docker安装es和kibana,单机模式
操作系统:mac系统 1.安装es docker pull elasticsearch:7.14.0 docker run --name es -p 9200:9200 -p 9300:9300 -e ...
- 使用taro+canvas实现微信小程序的图片分享功能
业务场景 二轮充电业务中,用户充电完成后在订单详情页展示订单相关信息,用户点击分享按钮唤起微信小程序分享菜单,将生成的图片海报分享给微信好友或者下载到本地,好友可通过扫描海报中的二维码加群领取优惠. ...
- Nodejs 应用编译构建提速建议
编译构建的整体过程 拉取编译镜像 拉取缓存镜像 拉取项目源码 挂载缓存目录 执行编译命令(用户自定义) 持久化缓存 上传编译镜像 为什么在本地构建就快, 但编译机上很慢 在编辑机上每次的构建环境都是全 ...
- # 代码随想录算法训练营Day4|24.两两交换链表中的节点 19.删除链表的倒数第N个节点 面试题02.07.链表相交 142.环形链表Ⅱ
24.两两交换链表中的节点 题目链接:24.两两交换链表中的节点 总体思路: 两两交换链表中的节点使用虚拟头节点可以更方便地进行交换,这样头节点和普通节点可以以同一种方式进行. 虚拟头结点的建设代码: ...