现象

某一个周末我们的服务 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 未关闭导致的内存泄漏的更多相关文章

  1. 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 ...

  2. 在Activity中使用Thread导致的内存泄漏

    https://github.com/bboyfeiyu/android-tech-frontier/tree/master/issue-7/%E5%9C%A8Activity%E4%B8%AD%E4 ...

  3. static关键字所导致的内存泄漏问题

    大家都知道内存泄漏和内存溢出是不一样的,内存泄漏所导致的越来越多的内存得不到回收的失手,最终就有可能导致内存溢出,下面说一下使用staitc属性所导致的内存泄漏的问题. 在dalvik虚拟机中,sta ...

  4. vue自定义指令导致的内存泄漏问题解决

    vue的自定义指令是一个比较容易引起内存泄漏的地方,原因就在于指令通常给元素绑定了事件,但是如果忘记了解绑,就会产生内存泄漏的问题. 看下面代码: directives: { scroll: { in ...

  5. 使用block的时候,导致的内存泄漏

    明确,只要在block里边用到我们自己的东西,成员变量,self之类的,我们都需要将其拿出来,把它做成弱指针以便之后进行释放. 在ZPShareViewController这个控制器中,由如下代码: ...

  6. 精华阅读第 13 期 |常见的八种导致 APP 内存泄漏的问题

    本期是移动开发精英俱乐部的第13期文章,都是以技术为主,所以这里就不过多的进行赘述了,我们直接看干货内容吧!本文系ITOM管理平台OneAPM整理. 实际项目中的MVVM(积木)模式–序章 导读:开篇 ...

  7. 一个驱动导致的内存泄漏问题的分析过程(meminfo->pmap->slabtop->alloc_calls)

    关键词:sqllite.meminfo.slabinfo.alloc_calls.nand.SUnreclaim等等. 下面记录一个由于驱动导致的内存泄漏问题分析过程. 首先介绍问题背景,在一款嵌入式 ...

  8. 使用HandyJSON导致的内存泄漏问题相关解决方法

    在移动开发中,与服务器打交道是不可避免的,从服务器拿到的接口数据最终都会被我们解析成模型,现在比较常见的数据传输格式是json格式,对json格式的解析可以使用原生的解析方式,也可以使用第三方的,我们 ...

  9. iOS - Block产生Memory Leaks循环引用导致的内存泄漏以及解决方案

    在ARC(自动引用技术)前,Objective-c都是手动来分配释放 释放 计数内存,其过程非常复杂. ARC技术推出后,貌似世界和平了很多,但是其实ARC并不等同于Java或者C#中的垃圾回收,AR ...

  10. android Context 持有导致的内存泄漏

    Context使用场景 为了防止Activity,Service等这样的Context泄漏于一些生命周期更长的对象,可以使用生命周期更长的ApplicationContext,但是不是所有的Conte ...

随机推荐

  1. blog图片资源

  2. AI 绘画咒语入门 - Stable Diffusion Prompt 语法指南 【成为初级魔导士吧!】

    要用好 Stable Diffusion,最最重要的就是掌握 Prompt(提示词).由于提示词对于生成图的影响甚大,所以被称为魔法,用得好惊天动地,用不好魂飞魄散 . 因此本篇整理下提示词的语法(魔 ...

  3. MySQL高频面试题

    什么是MySQL MySQL是一个关系型数据库,它采用表的形式来存储数据.你可以理解成是Excel表格,既然是表的形式存储数据,就有表结构(行和列).行代表每一行数据,列代表该行中的每个值.列上的值是 ...

  4. 一次redis主从切换导致的数据丢失与陷入只读状态故障

    背景 最近一组业务redis数据不断增长需要扩容内存,而扩容内存则需要重启云主机,在按计划扩容升级执行主从切换时意外发生了数据丢失与master进入只读状态的故障,这里记录分享一下. 业务redis高 ...

  5. MYSQL数据库的创建和删除

    打开Windows命令行,输入登录用户和密码 mysql -h localhost -u root -p 创建新数据 CREATE DATABASE zoo; 查看系统中的数据库 SHOW DATAB ...

  6. filler 抓取手机app的数据,手机wifi设置

    1.处于同一局域网下, 2.手机的代服务器修改为手动 3.代理服务器,名称为本机ip地址端口为8888,可以自己设置 4.fillder上面选择允许远程链接

  7. windows内核学习一

    变量类型 kernel user ULONG unsigned long PULONG unsigned long* UCHAR unsigned char PUCHAR unsigned char* ...

  8. 杭电多校第二场 DOS Card

    杭电多校第二场 DOS Card 评价一下这道题:我写过最爽的线段树题. 这道题真的非常令人身心愉悦,非常厉害的一道线段树入门题.我写这个一次调试都没有,过了样例就交了就过了,一切都是行云流水. 这道 ...

  9. 暑期实习开始啦「GitHub 热点速览」

    无巧不成书,刚好最近有小伙伴在找实习,而 GitHub 热榜又有收录实习信息的项目在榜.所以,无意外本周特推就收录了这个实习项目,当然还有国内版本.除了应景的实习 repo 之外,还有帮你管理文件的 ...

  10. 深入解析React DnD拖拽原理,轻松掌握拖放技巧!

    我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品.我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值.. 本文作者:霁明 一.背景 1.业务背景 业务中会有一些需要实现拖拽 ...