起因

今天在做数据库数据读取时, 首先通过多个 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 的回收时机无法控制, 我们能做的就是在代码层面保证要用的数据不会被回收.

最新文章

  1. 线程池 Threadpool (还需要补充)
  2. 软件工程:Wordcount程序作业
  3. java 入门 第二季3
  4. DropDownList 获取不了选择的值 这种错误
  5. 关于sql server 2008过期导致 MSSQLSERVER服务就无法启动,手动启动就报告错误代码17051。
  6. c# XML省市联动
  7. JavaScript —— 对象的取值与赋值
  8. How many ways?? - hdu2157(矩阵快速幂-模板)
  9. atnodes命令+sort+uniq统计特征信息到结果文件
  10. 基于webpack搭建的vue+element-ui框架
  11. JAVA9模块化详解(二)——模块的使用
  12. python的标准数据类型
  13. iOS Runloop理解
  14. sessions
  15. lnmp/nginx系统真正有效的图片防盗链完整设置详解
  16. sql server在执行批处理时出现错误。错误消息为: 目录名无效
  17. bzoj3040
  18. Useful Field of View (UFOV)
  19. g2蚂蚁数据可视化折线图,点位坐标label 图形文本设置
  20. 图片上传Security Error

热门文章

  1. Linux系统下常见的数据盘分区丢失的问题以及对应的处理方法
  2. 花 1 小时,开源设计 LoRa 继电器开关
  3. 前端开发:这10个Chrome扩展你不得不知
  4. 实用,Windows后台守护进程iNeuDaemon发布。Linux操作系统下使用使用supervisor
  5. clr via c# 运行时序列化
  6. MySQL安全管理
  7. 百度架构师带你进阶高级JAVA架构,让你快速从代码开发者成长为系统架构者
  8. 使用ffmpeg为影片添加字幕
  9. .Net框架的模块代码生成器--其二(dotnet tool)
  10. 安装npm install时,长时间停留在fetchMetadata的解决方法