内容简介:在介绍之前先说明一下,标题中带有【beego】标签的,是beego框架使用中遇到的坑。如果没有,那就是golang本身的坑。当然,此坑并非人家代码有问题,有几个地方反而是出于性能等各方面的考量有意而为之。但这些地方却是一门语言或框架的初学者大概率会遇到的困惑。在go中,slice/map的迭代循环的常用方式为:这其中value就是遍历时的当前值。
在介绍之前先说明一下,标题中带有【beego】标签的,是beego框架使用中遇到的坑。如果没有,那就是golang本身的坑。当然,此坑并非人家代码有问题,有几个地方反而是出于性能等各方面的考量有意而为之。但这些地方却是一门语言或框架的初学者大概率会遇到的困惑。
1、slice/map遍历时的修改问题
在 go 中,slice/map的迭代循环的常用方式为:
slice: for index,value := range _slice { } map: for key,value := range _map { }
这其中value就是遍历时的当前值。
按照我们之前在 java 中的遍历习惯,当遍历到第几个时,拿到的就是指向第几个对象的引用,因此对对象的所有修改行为本质上修改的都是原值。但是在这里并不是。看一个例子:
//tag1 var structs = []testModel{ testModel{ A: "一", B: "一一", C: 1, }, testModel{ A: "二", B: "二二", C: 2, }, testModel{ A: "三", B: "三三", C: 3, }, } //tag2 for _, val := range structs { val.A = "四" val.B = "四四" val.C = 4 } //tag3 for _, val := range structs { fmt.Print(val.A) fmt.Print(val.B) fmt.Println(val.C) }
代码逻辑很简单。
- 我们在tag1处定义了一个数组,里面包含三个testModel实例。每个testModel实例有三个字段,并且他们的字段值都是互不相同的。
- 按照事先的想法,是要在tag2处将所有testModel实例的字段值修改为相同的。
-
tag3检查一下修改结果。
结果console输出如下;
=== RUN TestLogic 一一一1 二二二2 三三三3 --- PASS: TestLogic (0.00s) PASS
打印的仍然是旧值。那这是为什么呢?
原因是:在range遍历时,map中的key&value,slice中的index&value,都是新的临时变量,这个临时变量被每一次迭代所共用,临时变量的值也是由被遍历元素复制而来。因此在该变量上修改是无效的。
那如何才能有效呢? 很简单,对上例的tag2部分做如下修改:
for key, _ := range structs { structs[key].A = "四" structs[key].B = "四" structs[key].C = 4 }
同理Slice修改时也需要用slice[index].column = newValue 的方式进行。
2、map/slice遍历时多协程问题(multi-goroutines)
在map遍历时,我们有可能会在map中使用go routines进行一些操作,例如下面这个例子:
var values = []int{1, 2, 3, 4, 5, 6, 7, 8, 9} var block = make(chan int, 2) //wrong for i, val := range values { go func() { fmt.Println(1000 + val) if i == len(values)-1 { block <- 1 } }() } for i := 0; i < 2; i++ { <-block } }
我们想用map中迭代的当前值,在协程中做一番大事业,潜意识的写法可能就是按照wrong中的写法那样,直接把value拿过来就用。但是却得到这样的结果:
=== RUN TestLogic 1009 1009 1009 1009 1009 1009 1009 1009 1009 --- PASS: TestLogic (0.00s) PASS
也就是说,在goroutine中的val,值竟然都是map遍历的最后一个!导致这一现象的原因有两个:
- for range下的迭代变量val的值是共用的,这一点在《slice/map遍历时修改问题 》中有提到
-
main函数所在的goroutine和后续启动的goroutines存在竞争关系
为了证实这一点,修改代码为如下:
for i, val := range values { fmt.Println(&val) go func() { fmt.Println(1000 + val) if i == len(values)-1 { block <- 1 } }() }
加了一行代码,打印val的内存地址,结果如下:
0xc000287738 0xc000287738 0xc000287738 0xc000287738 0xc000287738 0xc000287738 0xc000287738 0xc000287738 0xc000287738 1005 1009 1009 1009 1009 1009
val的地址在每次遍历时是同一个!证明第一点;goroutines在不同的遍历中存在变化,例如1005,证明第二点;当然也可用go run -trace ***.go 命令来查看协程的变化,就不多赘述。
那如何修改呢?只需要使用 函数参数复制 做一次数据复制即可,而不是闭包:
for i, val := range values { go func(val int) { fmt.Println(2000 + val) if i == len(values)-1 { block <- 1 } }(val) }
关于map遍历时多协程并发问题也可参考: https://github.com/golang/go/wiki/CommonMistakes
3、数组与值拷贝
首先把结论放在这,然后再展开讨论:
go语言数组的一切传递都是值拷贝,包括但不限于以下三个方面:
- 1、数组之间的直接赋值。
- 2、数组作为函数参数。
- 3、数组内嵌到struct中。
数组之间的直接赋值
看下面一段代码:
a := []int{1,2,3} //值复制 b := a fmt.Printf("%p, %v\n", &a, a) //0xc0000bf660, [1 2 3] fmt.Printf("%p, %v\n", &b, b) //0xc0000bf680, [1 2 3] a = append(a, 4) a[0] = 4 fmt.Println(len(b))//3 for e := range a {//0123 fmt.Print(e) }
首先定义了一个数组a,a中有3个元素。然后通过一次赋值操作,将a赋值给了b。
在java中,数组之间的赋值,是引用的传递,在a中修改后再通过b进行打印输出,会得到修改后的值。但是刚才说过,go中数组之间的复制操作是值拷贝。因此打印b仍然还是修改前的样子,会发现a和b在内存中是完全不同的两块内存区域。
数组作为函数参数传递
因为golang中函数参数的传递都是值拷贝,因此这一点放在数组上也不难理解。
在如上代码添加一句:test4(a)
a := []int{1,2,3} //值复制 b := a fmt.Printf("%p, %v\n", &a, a) //0xc0000bf660, [1 2 3] fmt.Printf("%p, %v\n", &b, b) //0xc0000bf680, [1 2 3] a = append(a, 4) a[0] = 4 fmt.Println(len(b))//3 for e := range a {//0123 fmt.Print(e) } test4(a)
其中test4代码如下:
func test4(param []int) { fmt.Printf("%p, %v\n", ¶m, param) //01230xc0000bf700, [4 2 3 4] }
会发现a & b & c各有各的内存地址~~
数组内嵌到struct中
a := []string{"1", "22"} var c = struct { S []string }{ S: []string{"1", "22"}, } //结构是值拷贝,内部的数组也是值拷贝 b := c //修改c中的数组元素值不影响b c.S[0] = "2" //修改b中的数组元素不影响c b.S[0] = "3" //地址不相同,说明每一个变量在内存中是独立的内存区域 fmt.Printf("%p,%v\n", &a, a) //0xc0000bd660,[1 22] fmt.Printf("%p,%v\n", &b.S, b.S) //0xc0000bd720,[3 22] fmt.Printf("%p,%v\n", &c.S, c.S) //0xc0000bd6a0,[3 22]
以上所述就是小编给大家介绍的《细数在用golang&beego做api server过程中的坑(一)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 存储过程 – 重新编译后,存储过程运行得很快
- 面试:谈谈你对 MyBatis 执行过程之 SQL 执行过程理解
- 死磕Android_App 启动过程(含 Activity 启动过程)
- 【PHP源码学习】关于$a=1整个过程的gdb过程与相关验证
- Spring的Property配置加载和使用过程及Environment的初始化过程
- [译]从输入URL到页面呈现的超详细过程——第二步:Tags转化成DOM的过程
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
像计算机科学家一样思考Python (第2版)
[美] 艾伦 B. 唐尼 / 赵普明 / 人民邮电出版社 / 2016-7 / 49.00
本书以培养读者以计算机科学家一样的思维方式来理解Python语言编程。贯穿全书的主体是如何思考、设计、开发的方法,而具体的编程语言,只是提供了一个具体场景方便介绍的媒介。 全书共21章,详细介绍Python语言编程的方方面面。本书从基本的编程概念开始讲起,包括语言的语法和语义,而且每个编程概念都有清晰的定义,引领读者循序渐进地学习变量、表达式、语句、函数和数据结构。书中还探讨了如何处理文件和......一起来看看 《像计算机科学家一样思考Python (第2版)》 这本书的介绍吧!