一、錯誤代碼示例
package main
import (
"fmt"
"time"
)
type TestUser struct {
Name string
Age int
}
type TestData struct {
Data map[string]interface{}
Sort int
}
var TestChan chan *TestData
func init() {
TestChan = make(chan *TestData, 100)
Start()
}
func main() {
arr := make([]*TestUser, 0)
a := &TestUser{Name: "aaa", Age: 12}
arr = append(arr, a)
b := &TestUser{Name: "bbb", Age: 13}
arr = append(arr, b)
c := &TestUser{Name: "ccc", Age: 14}
arr = append(arr, c)
data := map[string]interface{}{"test": "指針Map容器"}
for k, user := range arr {
miniData := data
miniData["user_name"] = user.Name
miniData["user_age"] = user.Age
d := &TestData{Data: miniData, Sort: k}
fmt.Println("通道加入數據", d)
TestChan <- d
}
time.Sleep(5 * time.Second)
}
func Start() {
go func() {
for {
data := <-TestChan
fmt.Println("通道取出數據", data)
}
}()
}
打印結果:
通道加入數據 &{map[descr:指針Map容器 user_age:12 user_name:aaa] 0}
通道加入數據 &{map[descr:指針Map容器 user_age:13 user_name:bbb] 1}
通道加入數據 &{map[descr:指針Map容器 user_age:14 user_name:ccc] 2}
通道取出數據 &{map[descr:指針Map容器 user_age:14 user_name:ccc] 0}
通道取出數據 &{map[descr:指針Map容器 user_age:14 user_name:ccc] 1}
通道取出數據 &{map[descr:指針Map容器 user_age:14 user_name:ccc] 2}
二、錯誤現象分析
上面的錯誤代碼是按照實際遇到問題的業務代碼邏輯寫出來的,如果耐心看完上面的例子,應該能看出,加入 Channel 前打印的數據和從 Channel 取出來的數據不一致,而這裏邏輯很簡單,只是加入了一次 Channel,取出的數據就變了。
仔細觀察,從 Channel 取出的數據,user_age 和 uer_name 都是循環最後一次遍歷賦的值,爲什麼會這樣?
三、錯誤原因說明
知識點:golang 中操作 map 、切片時,都是操作的變量對應的內存地址。
例子中的 data 參數類型爲 map[string]interface{},將 data 賦值給 miniData 時,因爲 golang 會操作對應的內存地址,這裏就是直接把 data 變量的內存地址賦值給 miniData,所以操作 miniData 時,實際就是操作的 data。
所以在每次循環時,代碼效果解釋如下:
miniData := data
// data地址賦值給miniData
miniData["user_name"] = user.Name
// 等價於 data["user_name"] = user.Name
miniData["user_age"] = user.Age
// 等價於 data["user_name"] = user.Name
d := &TestData{Data: miniData, Sort: k}
// 等價於 d := &TestData{Data: data, Sort: k}
TestChan <- d
// 加入 Channel 中的數據,Data 值同樣是最開始那個 data 的內存地址
取數據時:
data := <-TestChan
取數據的結果要看執行速度的情況;
如果上面到第二次循環時,此時 Channel 就取出一條數據,那對應打印的 Data 信息應該爲循環第二次賦值的 data 數據值,即:&{map[descr:指針Map容器 user_age:13 user_name:bbb] 1};
當前案例打印結果很明顯是 Channel 數據全部添加後,再從 Channel 依次取出數據,所以結果都爲第三次循環修改的數據,user_age:14 user_name:ccc。
四、解決方案
知道錯誤原因,解決方案就很明瞭了,新建一個 Map 容器,把需要賦值的實際數據傳入 Map,修改如下:
package main
import (
"fmt"
"time"
)
type TestUser struct {
Name string
Age int
}
type TestData struct {
Data map[string]interface{}
Sort int
}
var TestChan chan *TestData
func init() {
TestChan = make(chan *TestData, 100)
Start()
}
func main() {
arr := make([]*TestUser, 0)
a := &TestUser{Name: "aaa", Age: 12}
arr = append(arr, a)
b := &TestUser{Name: "bbb", Age: 13}
arr = append(arr, b)
c := &TestUser{Name: "ccc", Age: 14}
arr = append(arr, c)
data := map[string]interface{}{"test": "指針Map容器"}
for k, user := range arr {
// ------------------修改---------------
miniData := make(map[string]interface{}, 1)
for k, v := range data {
miniData[k] = v
}
// ------------------修改----------------
miniData["user_name"] = user.Name
miniData["user_age"] = user.Age
d := &TestData{Data: miniData, Sort: k}
fmt.Println("通道加入數據", d)
TestChan <- d
}
time.Sleep(5 * time.Second)
}
func Start() {
go func() {
for {
data := <-TestChan
fmt.Println("通道取出數據", data)
}
}()
}
打印結果:
通道加入數據 &{map[test:指針Map容器 user_age:12 user_name:aaa] 0}
通道加入數據 &{map[test:指針Map容器 user_age:13 user_name:bbb] 1}
通道加入數據 &{map[test:指針Map容器 user_age:14 user_name:ccc] 2}
通道取出數據 &{map[test:指針Map容器 user_age:12 user_name:aaa] 0}
通道取出數據 &{map[test:指針Map容器 user_age:13 user_name:bbb] 1}
通道取出數據 &{map[test:指針Map容器 user_age:14 user_name:ccc] 2}
五、總結
golang 的變量類型可以分爲 值類型 與 指針類型 兩種:
- 值類型 變量直接指向存在內存中的值,數據類型包括 int 系列、float 系列、bool、 string 、數組 和 結構體 struct;
- 指針類型 變量指向的是內存地址,數據類型包括 指針、slice 切片、map、管道 chan、interface。
操作指針類型變量時,一定要注意當前案例賦值傳值的問題!