起因

今天在做数据库数据读取时, 首先通过多个 goroutine 将从数据库读取的数据写入 channel, 同时通过另一个 goroutine 从 channel 中读取数据进行分析.

就是这么简单的一个功能, 在读取数据的时候不定期的会出如下错误:

[signal SIGSEGV: segmentation violation code=0x1 addr=0x7f2227fe004d pc=0x52eb6f]

原因调查

数据库是 boltdb, 错误的位置总是出在 json.Unmarshal 的地方:

1  for v := range outCh {
2 var data OmsData
3 if err := json.Unmarshal(v, &data); err != nil {
4 log.Fatalf("json unmarshal error: %v\n", err)
5 }
6 }

outCh 中就是从数据库读取的数据. 刚开始以为是数据中的数据有错误, 后来发现 err 也捕获不到, 每次都是 panic 错误.

于是, 就分析了下整个过程, 读取数据的 goroutine 代码大致如下:

 1  func readOneDB(db *bolt.DB, outCh chan []byte) {
2 defer db.Close()
3
4 // 获取 db 中的所有 bucket
5 bucketNames := getAllBucketNames(db)
6
7 err := db.View(func(tx *bolt.Tx) error {
8
9 for _, bName := range bucketNames {
10
11 bucket := tx.Bucket([]byte(bName))
12
13 bucket.ForEach(func(_ []byte, v []byte) error {
14 // 把 bucket 中的value 写入 channel
15 outCh <- v
16 return nil
17 })
18 }
19
20 return nil
21 })
22
23 if err != nil {
24 log.Fatal(err)
25 }
26 }

读取数据的代码也很简单, 没有明显的问题.

原因分析

读写 channel 的代码就是上面那么简单, 一眼就能看明白, 为什么会 panic? 我进行了多次实验, 发现如下现象:

  1. 每次 panic 的时候, json.Unmarshal 收到的数据不一样, 也就是 panic 不是发生在固定的数据上
  2. 发生 panic 的时候, 都是在数据读取完之后, 也就是上面的 readOneDB 执行完之后
  3. 如果 channel 的容量小, 很难出现 panic, 如果 channel 的容量大(比如 10000 以上, make(chan []byte, 10000)), 就容易出现 panic
  4. boltdb 总体数据量(80 万条)不算小, 如果数据量小的库, 不会出现 panic

基于上面的分析, 我当时就觉得是不是 db.Close() 之后, 把写入 channel 的一些数据也释放了.

问题解决

于是, 我尝试在写入 channel 之前, 把数据复制一份, 改造 readOneDB 如下:

 1  func readOneDB(db *bolt.DB, outCh chan []byte) {
2 defer db.Close()
3
4 bucketNames := getAllBucketNames(db)
5
6 err := db.View(func(tx *bolt.Tx) error {
7
8 for _, bName := range bucketNames {
9
10 bucket := tx.Bucket([]byte(bName))
11
12 bucket.ForEach(func(_ []byte, v []byte) error {
13 // ** 改造的部分 **
14 // 改造的方式就是把 bucket 中的数据copy一份放入channel
15 // 而不是像之前那样, 直接把 v 放入 channel
16 nb := make([]byte, len(v))
17 copy(nb, v)
18 outCh <- nb
19 return nil
20 })
21 }
22
23 return nil
24 })
25
26 if err != nil {
27 log.Fatal(err)
28 }
29 }

这样改造之后, 就再也没有出现内存错误了!

总结

golang 的 channel 中写入数据的时候, 如果写入的是引用类型, 那么应该写入的是数据的地址, 而不是完整的数据, 如果该地址对应的数据被 GC 回收的话, 在使用数据的地方就会导致 内存错误(panic)

这种问题很隐蔽, 因为 GC 的回收时机无法控制, 我们能做的就是在代码层面保证要用的数据不会被回收.

golang channel 的一次内存错误的更多相关文章

  1. golang channel原理

    channel介绍 channel一个类型管道,通过它可以在goroutine之间发送和接收消息.它是Golang在语言层面提供的goroutine间的通信方式. 众所周知,Go依赖于称为CSP(Co ...

  2. golang channel底层结构和实现

    一.介绍 Golang 设计模式: 不要通过共享内存来通信,而要通过通信实现内存共享 channel是基于通信顺序模型(communication sequential processes, CSP) ...

  3. C/C++ char a[ ] 和 char *a 的差别,改变 char *a爆内存错误的原因

    对于一些需要传入参数为 char * temp 指针类的函数: 我们定义一个 char a[10] 或char *a 传进去都是可以的. 但是, 如果该函数是会改变你所传入的参数的值时, 传入 cha ...

  4. iOS 内存错误调试(EXC_BAD_ACCESS)

    内存错误crash现场: Thread堆栈: 有可能是访问被释放对象造成,根据现场并不能找到具体哪个对象出现内存错误. 1.开启僵尸对象调试 Edit Scheme->Debug->Dia ...

  5. setter方法的内存错误

    - (void)setList:(ClassicList *)list { self.list = list; _titleLabel.text = list.activityName; _addre ...

  6. spark分片个数的确定及Spark内存错误(GC error)的迂回解决方式

    我们知道,spark中每个分片都代表着一部分数据,那么分片数量如何被确认的呢? 首先我们使用最常见的HDFS+Spark,sparkDeploy的方式来讨论,spark读取HDFS数据使用的是spar ...

  7. [转]C++常见内存错误汇总

    在系统开发过程中出现的bug相对而言是比较好解决的,花费在这个上面的调试代价不是很大,但是在系统集成后的bug往往是难以定位的bug(最好方式是打桩,通过打桩可以初步锁定出错的位置,如:进入函数前打印 ...

  8. 教程-在F9后提示内存错误,点击了乎略,之后怎么取消乎略?

    问题现象:F9后,调试程序,提示内存错误,点击了“乎略”.之后再也没有出现错误了.可是想改这个BUG时,没法取消乎略了. 问题原因:在DLEPHI的选项中是这么一个地方是可以设置的. 问题处理:打开D ...

  9. 问题-关于sharemem程序访问WEB出现内存错误处理

    [delphi技术] 关于sharemem造成dll错误的处理办法问题现象:如果程序和dll之间用string作为参数传递时容易出现错误问题处理:需要在程序的uses中使用sharemem.这个sha ...

随机推荐

  1. 高并发之——深入解析Callable接口

    本文纯干货,从源码角度深入解析Callable接口,希望大家踏下心来,打开你的IDE,跟着文章看源码,相信你一定收获不小. 1.Callable接口介绍 Callable接口是JDK1.5新增的泛型接 ...

  2. 珠峰-架构6-es6

    let aa = ; { console.log(aa); } // ----- let aa = ; { console.log(aa); // 报错 aa is not defined let a ...

  3. .NET CORE应用程序启动

    ASP.NET Core 应用是在其 Main 方法中创建 Web 服务器的控制台应用: Main 方法调用 WebHost.CreateDefaultBuilder,通过生成器模式来创建web主机. ...

  4. 06.JS对象-1

    前言: 学习一门编程语言的基本步骤(01)了解背景知识(02)搭建开发环境(03)语法规范(04)常量和变量(05)数据类型(06)数据类型转换(07)运算符(08)逻辑结构(09)函数(10)对象1 ...

  5. Android中通过ImageSwitcher实现相册滑动查看照片功能(附代码下载)

    场景 效果 注: 博客: https://blog.csdn.net/badao_liumang_qizhi关注公众号 霸道的程序猿 获取编程相关电子书.教程推送与免费下载. 实现 将需要滚动查看的照 ...

  6. yum的repo的配置文件说明

    [base]:容器名称,一定要放在[]中.name:容器说明,可以自己随便写.mirrorlist:镜像站点,这个可以注释掉.baseurl:我们的 yum 源服务器的地址.默认是 CentOS 官方 ...

  7. Ubuntu-Server18.04开启无线网卡并配置静态ip

    手里有一个笔记本快10年了,还是奔腾处理器,最近把它做成了Ubuntu-Server的系统,花了点时间折腾无线网卡,稍微记录一下,希望看见的人能少踩点坑. 1. 制作U盘启动工具,从Ubuntu官网下 ...

  8. 笔记本磁盘中OEM分区的使用

    (1).开机进入系统前,按F8,进入Windows 10的高级启动选项,选择“修复计算机”. (2).选择键盘输入方法. (3).如果有管理员密码,需要输入:如果没有设置密码,直接“确定”即可. (4 ...

  9. CF1311E Construct the Binary Tree

    膜这场比赛的 \(rk1\) \(\color{black}A\color{red}{lex\_Wei}\) 这题应该是这场比赛最难的题了 容易发现,二叉树的下一层不会超过这一层的 \(2\) 倍,所 ...

  10. IIS WEB站点设置

    IIS安装 打开控制面板 -> 程序 -> 打开或关闭Windows功能 ,在弹出得对话框中选择“Internet信息服务”复选框.我这里是Windows server 2019 ,界面有 ...