今天在作數據庫數據讀取時, 首先經過多個 goroutine 將從數據庫讀取的數據寫入 channel, 同時經過另外一個 goroutine 從 channel 中讀取數據進行分析.golang
就是這麼簡單的一個功能, 在讀取數據的時候不按期的會出以下錯誤:數據庫
[signal SIGSEGV: segmentation violation code=0x1 addr=0x7f2227fe004d pc=0x52eb6f]
數據庫是 boltdb, 錯誤的位置老是出在 json.Unmarshal 的地方:json
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 錯誤.code
因而, 就分析了下整個過程, 讀取數據的 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 }
讀取數據的代碼也很簡單, 沒有明顯的問題.get
讀寫 channel 的代碼就是上面那麼簡單, 一眼就能看明白, 爲何會 panic? 我進行了屢次實驗, 發現以下現象:io
基於上面的分析, 我當時就以爲是否是 db.Close() 以後, 把寫入 channel 的一些數據也釋放了.class
因而, 我嘗試在寫入 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 }
這樣改造以後, 就再也沒有出現內存錯誤了!channel
golang 的 channel 中寫入數據的時候, 若是寫入的是引用類型, 那麼應該寫入的是數據的地址, 而不是完整的數據, 若是該地址對應的數據被 GC 回收的話, 在使用數據的地方就會致使 內存錯誤(panic)
這種問題很隱蔽, 由於 GC 的回收時機沒法控制, 咱們能作的就是在代碼層面保證要用的數據不會被回收.