起因

今天在做数据库数据读取时, 首先通过多个 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. Django (一) 基础

    创建项目 创建app     python manager.py startapp app01 修改.添加url from django.conf.urls import url,include fr ...

  2. H5异步加载多图

    异步加载多图(可能没啥用,加载慢)(图片预加载,提前给浏览器缓存图片) 1. 用一个计数变量记录需要加载的图片个数 2. 用new Image()去加载,加载完给此对象的src赋值要加载的url路径( ...

  3. 【C#】写文件时如何去掉编码前缀

    我们都知道,文件有不同的编码,例如我们常用的中文编码有:UTF8.GK2312 等. Windows 操作系统中,新建的文件会在起始部分加入几个字符的前缀,来识别编码. 例如,新建文本文件,写入单词 ...

  4. 12-Factor与云原生Part2

    12-Factor与云原生Part2 12-Factor 为构建如下的 SaaS 应用提供了方法论: 使用声明式格式来搭建自动化,从而使新的开发者花费最少的学习成本加入这个项目 和底层操作系统保持简洁 ...

  5. go微服务框架kratos学习笔记九(kratos 全链路追踪 zipkin)

    目录 go微服务框架kratos学习笔记九(kratos 全链路追踪 zipkin) zipkin使用demo 数据持久化 go微服务框架kratos学习笔记九(kratos 全链路追踪 zipkin ...

  6. HDU-1754 I Hate It (树状数组模板题——单点更新,区间查询最大值)

    题目链接 ac代码(注意字符读入前需要注意回车的影响) #include<iostream> #include<cstdio> #include<cstring> ...

  7. cf999E (强联通分量模板题)

    给出n个点m条边的有向图,问至少添加多少条边使得任何点都可以从s点出发可达 #include<bits/stdc++.h> #define forn(i, n) for (int i = ...

  8. [SDOI2018]反回文串

    题意 问有多少个长度为\(N\)且字符集大小为\(K\)的字符串可以通过回文串旋转 (把第一个字符移到最后)若干次得到.\(K\le N≤10^{18}\) 做法 ARC64F的加强版 设\(h(d) ...

  9. 吴裕雄--天生自然 R语言数据可视化绘图(1)

    par(ask=TRUE) opar <- par(no.readonly=TRUE) # make a copy of current settings attach(mtcars) # be ...

  10. linux 下生成 ssh 公私钥

    生成命令 ssh-keygen -t rsa -C "763941715@qq.com" 密匙目录 cd ~/.ssh 公钥 id_rsa.pub 私钥 id_rsa