一、错误代码示例
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。
操作指针类型变量时,一定要注意当前案例赋值传值的问题!