细数在用golang&beego做api server过程中的坑(一)

在介绍之前先说明一下,标题中带有【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", &param, 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]
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容

  • 转载自:https://halfrost.com/go_map_chapter_one/ https://half...
    HuJay阅读 6,124评论 1 5
  • Scala与Java的关系 Scala与Java的关系是非常紧密的!! 因为Scala是基于Java虚拟机,也就是...
    灯火gg阅读 3,421评论 1 24
  • Hello World这是开发的一个开发魔咒,几乎所有人都是从这开始的,今天我也从魔咒开始先打印一份Hello W...
    cocoaAhda阅读 1,429评论 0 3
  • 出处---Go编程语言 欢迎来到 Go 编程语言指南。本指南涵盖了该语言的大部分重要特性 Go 语言的交互式简介,...
    Tuberose阅读 18,403评论 1 46
  • 在《致我们终将逝去的青春》电影里面有一句话"我们都变成了自己曾经讨厌的人的样子",回想起当时的画面,场景略带些伤感...
    小溢_eea1阅读 254评论 1 1