細(xì)數(shù)在用golang&beego做api server過(guò)程中的坑(一)

在介紹之前先說(shuō)明一下彼哼,標(biāo)題中帶有【beego】標(biāo)簽的歇终,是beego框架使用中遇到的坑谢谦。如果沒(méi)有伐割,那就是golang本身的坑构灸。當(dāng)然戴而,此坑并非人家代碼有問(wèn)題收叶,有幾個(gè)地方反而是出于性能等各方面的考量有意而為之勒葱。但這些地方卻是一門(mén)語(yǔ)言或框架的初學(xué)者大概率會(huì)遇到的困惑写穴。

1惰拱、slice/map遍歷時(shí)的修改問(wèn)題

在go中,slice/map的迭代循環(huán)的常用方式為:

slice:
for index,value := range _slice {
}
map:
for key,value := range _map {
}

這其中value就是遍歷時(shí)的當(dāng)前值啊送。
按照我們之前在java中的遍歷習(xí)慣偿短,當(dāng)遍歷到第幾個(gè)時(shí)欣孤,拿到的就是指向第幾個(gè)對(duì)象的引用,因此對(duì)對(duì)象的所有修改行為本質(zhì)上修改的都是原值翔冀。但是在這里并不是导街。看一個(gè)例子:

//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)
    }

代碼邏輯很簡(jiǎn)單纤子。

  • 我們?cè)趖ag1處定義了一個(gè)數(shù)組搬瑰,里面包含三個(gè)testModel實(shí)例。每個(gè)testModel實(shí)例有三個(gè)字段控硼,并且他們的字段值都是互不相同的泽论。
  • 按照事先的想法,是要在tag2處將所有testModel實(shí)例的字段值修改為相同的卡乾。
  • tag3檢查一下修改結(jié)果翼悴。
    結(jié)果console輸出如下;
=== RUN   TestLogic
一一一1
二二二2
三三三3
--- PASS: TestLogic (0.00s)
PASS

打印的仍然是舊值。那這是為什么呢幔妨?
原因是:在range遍歷時(shí)鹦赎,map中的key&value,slice中的index&value误堡,都是新的臨時(shí)變量古话,這個(gè)臨時(shí)變量被每一次迭代所共用,臨時(shí)變量的值也是由被遍歷元素復(fù)制而來(lái)锁施。因此在該變量上修改是無(wú)效的陪踩。
那如何才能有效呢? 很簡(jiǎn)單悉抵,對(duì)上例的tag2部分做如下修改:

    for key, _ := range structs {
        structs[key].A = "四"
        structs[key].B = "四"
        structs[key].C = 4
    }

同理Slice修改時(shí)也需要用slice[index].column = newValue 的方式進(jìn)行肩狂。

2、map/slice遍歷時(shí)多協(xié)程問(wèn)題(multi-goroutines)

在map遍歷時(shí)姥饰,我們有可能會(huì)在map中使用go routines進(jìn)行一些操作傻谁,例如下面這個(gè)例子:

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中迭代的當(dāng)前值,在協(xié)程中做一番大事業(yè)列粪,潛意識(shí)的寫(xiě)法可能就是按照wrong中的寫(xiě)法那樣栅螟,直接把value拿過(guò)來(lái)就用。但是卻得到這樣的結(jié)果:

=== RUN   TestLogic
1009
1009
1009
1009
1009
1009
1009
1009
1009
--- PASS: TestLogic (0.00s)
PASS

也就是說(shuō)篱竭,在goroutine中的val力图,值竟然都是map遍歷的最后一個(gè)!導(dǎo)致這一現(xiàn)象的原因有兩個(gè):

  • for range下的迭代變量val的值是共用的掺逼,這一點(diǎn)在《slice/map遍歷時(shí)修改問(wèn)題 》中有提到
  • main函數(shù)所在的goroutine和后續(xù)啟動(dòng)的goroutines存在競(jìng)爭(zhēng)關(guān)系
    為了證實(shí)這一點(diǎn)吃媒,修改代碼為如下:
for i, val := range values {
        fmt.Println(&val)
        go func() {
            fmt.Println(1000 + val)
            if i == len(values)-1 {
                block <- 1
            }
        }()
    }

加了一行代碼,打印val的內(nèi)存地址,結(jié)果如下:

0xc000287738
0xc000287738
0xc000287738
0xc000287738
0xc000287738
0xc000287738
0xc000287738
0xc000287738
0xc000287738
1005
1009
1009
1009
1009
1009

val的地址在每次遍歷時(shí)是同一個(gè)赘那!證明第一點(diǎn)刑桑;goroutines在不同的遍歷中存在變化,例如1005募舟,證明第二點(diǎn)祠斧;當(dāng)然也可用go run -trace ***.go 命令來(lái)查看協(xié)程的變化,就不多贅述拱礁。
那如何修改呢琢锋?只需要使用 函數(shù)參數(shù)復(fù)制 做一次數(shù)據(jù)復(fù)制即可,而不是閉包:

    for i, val := range values {
        go func(val int) {
            fmt.Println(2000 + val)
            if i == len(values)-1 {
                block <- 1
            }
        }(val)
    }

關(guān)于map遍歷時(shí)多協(xié)程并發(fā)問(wèn)題也可參考:https://github.com/golang/go/wiki/CommonMistakes

3呢灶、數(shù)組與值拷貝

首先把結(jié)論放在這吴超,然后再展開(kāi)討論:
go語(yǔ)言數(shù)組的一切傳遞都是值拷貝,包括但不限于以下三個(gè)方面:

  • 1鸯乃、數(shù)組之間的直接賦值鲸阻。
  • 2、數(shù)組作為函數(shù)參數(shù)缨睡。
  • 3鸟悴、數(shù)組內(nèi)嵌到struct中。
數(shù)組之間的直接賦值

看下面一段代碼:

    a := []int{1,2,3}

    //值復(fù)制
    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)
    }

首先定義了一個(gè)數(shù)組a奖年,a中有3個(gè)元素细诸。然后通過(guò)一次賦值操作,將a賦值給了b拾并。
在java中揍堰,數(shù)組之間的賦值鹏浅,是引用的傳遞嗅义,在a中修改后再通過(guò)b進(jìn)行打印輸出,會(huì)得到修改后的值隐砸。但是剛才說(shuō)過(guò)之碗,go中數(shù)組之間的復(fù)制操作是值拷貝。因此打印b仍然還是修改前的樣子季希,會(huì)發(fā)現(xiàn)a和b在內(nèi)存中是完全不同的兩塊內(nèi)存區(qū)域褪那。

數(shù)組作為函數(shù)參數(shù)傳遞

因?yàn)間olang中函數(shù)參數(shù)的傳遞都是值拷貝,因此這一點(diǎn)放在數(shù)組上也不難理解式塌。
在如上代碼添加一句:test4(a)

    a := []int{1,2,3}

    //值復(fù)制
    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]
}

會(huì)發(fā)現(xiàn)a & b & c各有各的內(nèi)存地址~~

數(shù)組內(nèi)嵌到struct中
    a := []string{"1", "22"}
    var c = struct {
        S []string
    }{
        S: []string{"1", "22"},
    }

    //結(jié)構(gòu)是值拷貝博敬,內(nèi)部的數(shù)組也是值拷貝
    b := c

    //修改c中的數(shù)組元素值不影響b
    c.S[0] = "2"

    //修改b中的數(shù)組元素不影響c
    b.S[0] = "3"

    //地址不相同,說(shuō)明每一個(gè)變量在內(nèi)存中是獨(dú)立的內(nèi)存區(qū)域
    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]
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末峰尝,一起剝皮案震驚了整個(gè)濱河市偏窝,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖祭往,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件伦意,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡硼补,警方通過(guò)查閱死者的電腦和手機(jī)驮肉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)已骇,“玉大人离钝,你說(shuō)我怎么就攤上這事〖埠矗” “怎么了奈辰?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)乱豆。 經(jīng)常有香客問(wèn)我奖恰,道長(zhǎng),這世上最難降的妖魔是什么宛裕? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任瑟啃,我火速辦了婚禮,結(jié)果婚禮上揩尸,老公的妹妹穿的比我還像新娘蛹屿。我一直安慰自己,他們只是感情好岩榆,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布错负。 她就那樣靜靜地躺著,像睡著了一般勇边。 火紅的嫁衣襯著肌膚如雪犹撒。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,370評(píng)論 1 302
  • 那天粒褒,我揣著相機(jī)與錄音识颊,去河邊找鬼。 笑死奕坟,一個(gè)胖子當(dāng)著我的面吹牛祥款,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播月杉,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼刃跛,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了苛萎?” 一聲冷哼從身側(cè)響起桨昙,我...
    開(kāi)封第一講書(shū)人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤跌帐,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后绊率,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體谨敛,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年滤否,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了脸狸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡藐俺,死狀恐怖炊甲,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情欲芹,我是刑警寧澤卿啡,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站菱父,受9級(jí)特大地震影響颈娜,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜浙宜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一官辽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧粟瞬,春花似錦同仆、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至市怎,卻和暖如春岁忘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背焰轻。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工臭觉, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留昆雀,地道東北人辱志。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像狞膘,于是被迫代替她去往敵國(guó)和親揩懒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容