欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

大多數(shù)Go程序員都走過(guò)的坑盤(pán)點(diǎn)解析

 更新時(shí)間:2023年12月20日 08:57:06   作者:晁岳攀(鳥(niǎo)窩) 鳥(niǎo)窩聊技術(shù)  
這篇文章主要為大家介紹了大多數(shù)Go程序員都走過(guò)的坑盤(pán)點(diǎn)解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

循環(huán)變量

說(shuō)起每個(gè)程序員必犯的錯(cuò)誤,那還得是"循環(huán)變量"這個(gè)錯(cuò)誤了,就連 Go 的開(kāi)發(fā)者都犯過(guò)這個(gè)錯(cuò)誤,這個(gè)錯(cuò)誤在 Go 的 FAQ 中也有提到

What happens with closures running as goroutines?[1]:

func main() {
    var wg sync.WaitGroup

    values := []string{"a", "b", "c"}
    for _, v := range values {
        wg.Add(1)
        go func() {
            fmt.Println(v)
            wg.Done()
        }()
    }

    wg.Wait()
}

你可能期望能輸出a、b、c這三個(gè)字符(可能順序不同),但是實(shí)際可能輸出的是c、c、c。這是因?yàn)檠h(huán)變量的作用域是整個(gè)循環(huán),而不是單次迭代,所以在循環(huán)體中使用的變量是同一個(gè)變量,而不是每次迭代都是一個(gè)新的變量。

這個(gè)錯(cuò)誤有時(shí)候隱藏很深,即使沒(méi)有 goroutine,也有可能,比如下面的代碼,并沒(méi)有使用額外的 goroutine 和閉包,也是有問(wèn)題的:

package main
import (
 "fmt"
)
type Char struct {
 Char *string
}
func main() {
 var chars []Char
 values := []string{"a", "b", "c"}
 for _, v := range values {
  chars = append(chars, Char{Char: &v})
 }
 for _, v := range chars {
  fmt.Println(*v.Char)
 }
}

輸出也大概率是cc、c,因?yàn)榻o每個(gè)Char的字段賦值的是 v 的指針,v 在整個(gè)循環(huán)中都是一個(gè)變量,所以最后的結(jié)果都是c。

Go 團(tuán)隊(duì)很早也意識(shí)到這個(gè)問(wèn)題了,但是考慮到兼容的問(wèn)題,大家的容忍程度,那就這樣了。每個(gè) Go 程序員都在這里摔一跤,也就長(zhǎng)記性了,所以一直沒(méi)有改變這個(gè)設(shè)計(jì)。我在這里摔了好多跤,以至于我寫(xiě) for 循環(huán)的時(shí)候都戰(zhàn)戰(zhàn)兢兢的,和 Russ Cox 統(tǒng)計(jì)的網(wǎng)上的處理一樣,不管有無(wú)必要,很多時(shí)候我都是先把循環(huán)變量賦值給一個(gè)局部變量,然后再使用,比如下面的代碼:

for _, v := range values {
    v := v
    wg.Add(1)
    go func() {
        fmt.Println(v)
        wg.Done()
    }()
}

變量只在循環(huán)體中使用

今年 5 月份的時(shí)候,Russ Cox 忍不住了,提了一個(gè)提案#60078[2],提案的內(nèi)容是在 for 循環(huán)中,如果變量只在循環(huán)體中使用,那么就會(huì)在每次迭代中創(chuàng)建一個(gè)新的變量,而不是使用同一個(gè)變量。這個(gè)提案引起了很多人的關(guān)注,很多人都在討論這個(gè)提案,這個(gè)提案被接收了,具體提案內(nèi)容在文檔中Proposal: Less Error-Prone Loop Variable Scoping[3]。

如果你使用 Go 1.21, 你可以開(kāi)始這個(gè)功能,使用GOEXPERIMENT=loopvar go run main.go運(yùn)行上面的程序,會(huì)輸出c、ba這樣的輸出,不再是cc、c了。這個(gè)特性在 Go 1.22 中會(huì)默認(rèn)開(kāi)啟,不需要設(shè)置GOEXPERIMENT了。還有一兩個(gè)月才能正式發(fā)布 go 1.22,

大家可以使用 gotip 測(cè)試:

$ gotip run main.go
a
b
c

不只是for-range,下面的3-clause也是同樣的問(wèn)題:

func main() {
 var ids []*int
 for i := 0; i < 3; i++ {
  i = 10
 }

 for _, id := range ids {
  fmt.Println(*id)
 }
}

Go 1.22 中也會(huì)修復(fù)這個(gè)問(wèn)題。

對(duì)比C#語(yǔ)言

C#語(yǔ)言就只修改了for-range語(yǔ)句,3-clause語(yǔ)句就沒(méi)有修改, Go 兩種都做了修改。

但是, 問(wèn)題就來(lái)了哈,像下面的代碼,Go 1.22 和以前的代碼會(huì)一樣么?

func main() {
 var ids []*int
 for i := 0; i < 3; i++ {
        i = 10
  ids = append(ids, &i)
 }

 for _, id := range ids {
  fmt.Println(*id)
 }
}

如果用 Go 1.21,它會(huì)輸出11。如果用 Go 1.22,它會(huì)輸出10。原因還是在于這個(gè)提案實(shí)現(xiàn)后,每次迭代的時(shí)候,都會(huì)創(chuàng)建一個(gè)新的變量,所以ids中的元素都是指向不同的變量,而不是同一個(gè)變量。

看起來(lái)打破了向下兼容的承諾,你如果先前就想利用這個(gè) corner case 的話,Go1.22 已經(jīng)不兼容了。

更進(jìn)一步,你會(huì)發(fā)現(xiàn)再執(zhí)行3-clause的第三條 clause 的時(shí)候,變量已經(jīng)被重新創(chuàng)建,比如下面的代碼:

func main() {
 for i, p := 0, (*int)(nil); i < 3; println("3rd-clause:", &i, p) {
  p = &i
  fmt.Println("loop body:", &i, p)
  i++
 }
}

輸出:

$gotip run main.go
loop body: 0x14000120018 0x14000120018
3rd-clause: 0x14000120030 0x14000120018 // &i已經(jīng)變?yōu)?x14000120030
loop body: 0x14000120030 0x14000120030
3rd-clause: 0x14000120038 0x14000120030 // &i已經(jīng)變?yōu)?x14000120038
loop body: 0x14000120038 0x14000120038
3rd-clause: 0x14000120040 0x14000120038 // &i已經(jīng)變?yōu)?x14000120040

參考資料

https://github.com/golang/go/issues/60078

https://go.dev/doc/faq#closures_and_goroutines 

以上就是大多數(shù)Go程序員都走過(guò)的坑盤(pán)點(diǎn)解析的詳細(xì)內(nèi)容,更多關(guān)于Go程序員都走過(guò)的坑的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • golang值類(lèi)型轉(zhuǎn)換成[]uint8類(lèi)型的操作

    golang值類(lèi)型轉(zhuǎn)換成[]uint8類(lèi)型的操作

    這篇文章主要介紹了golang值類(lèi)型轉(zhuǎn)換成[]uint8類(lèi)型的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-05-05
  • Go語(yǔ)言時(shí)間處理必備技巧全解析

    Go語(yǔ)言時(shí)間處理必備技巧全解析

    Golang 的時(shí)間處理是 Golang 編程中的一個(gè)重要方面,它涉及到了時(shí)間類(lèi)型、時(shí)間格式化、時(shí)間計(jì)算、時(shí)區(qū)處理以及定時(shí)器和超時(shí)機(jī)制等多個(gè)方面。在本文中,我們將從更深入的角度來(lái)探討 Golang 的時(shí)間處理
    2023-04-04
  • 解析golang 標(biāo)準(zhǔn)庫(kù)template的代碼生成方法

    解析golang 標(biāo)準(zhǔn)庫(kù)template的代碼生成方法

    這個(gè)項(xiàng)目的自動(dòng)生成代碼都是基于 golang 的標(biāo)準(zhǔn)庫(kù) template 的,所以這篇文章也算是對(duì)使用 template 庫(kù)的一次總結(jié),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧
    2021-11-11
  • 詳解golang 定時(shí)任務(wù)time.Sleep和time.Tick實(shí)現(xiàn)結(jié)果比較

    詳解golang 定時(shí)任務(wù)time.Sleep和time.Tick實(shí)現(xiàn)結(jié)果比較

    本文主要介紹了golang 定時(shí)任務(wù)time.Sleep和time.Tick實(shí)現(xiàn)結(jié)果比較,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-02-02
  • Go語(yǔ)言實(shí)現(xiàn)Fibonacci數(shù)列的方法

    Go語(yǔ)言實(shí)現(xiàn)Fibonacci數(shù)列的方法

    這篇文章主要介紹了Go語(yǔ)言實(shí)現(xiàn)Fibonacci數(shù)列的方法,實(shí)例分析了使用遞歸和不使用遞歸兩種技巧,并對(duì)算法的效率進(jìn)行了對(duì)比,需要的朋友可以參考下
    2015-02-02
  • golang 阻止主goroutine退出的操作

    golang 阻止主goroutine退出的操作

    這篇文章主要介紹了golang 阻止主goroutine退出的操作方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-04-04
  • Golang開(kāi)發(fā)中如何解決共享變量問(wèn)題

    Golang開(kāi)發(fā)中如何解決共享變量問(wèn)題

    Go提供了傳統(tǒng)通過(guò)共享變量,也就是共享內(nèi)存的方式來(lái)實(shí)現(xiàn)并發(fā)。這篇文章會(huì)介紹 Go提供的相關(guān)機(jī)制,對(duì)Golang共享變量相關(guān)知識(shí)感興趣的朋友一起看看吧
    2021-09-09
  • Go數(shù)組的具體使用

    Go數(shù)組的具體使用

    Go語(yǔ)言中的數(shù)組是一種固定長(zhǎng)度的數(shù)據(jù)結(jié)構(gòu),它包含一組按順序排列的元素,每個(gè)元素都具有相同的類(lèi)型,本文主要介紹了Go數(shù)組的具體使用,包括聲明數(shù)組、初始化數(shù)組、訪問(wèn)數(shù)組元素等,感興趣的可以了解下
    2023-11-11
  • Go泛型的理解和使用小結(jié)

    Go泛型的理解和使用小結(jié)

    泛型是一種非常強(qiáng)大的編程技術(shù),可以提高代碼的復(fù)用性和可讀性,通過(guò)泛型容器和類(lèi)型參數(shù)化,Go語(yǔ)言中的泛型可以實(shí)現(xiàn)更加靈活和通用的編程,提高代碼的復(fù)用性和可維護(hù)性,本文給大家介紹Go泛型的理解和使用,感興趣的朋友一起看看吧
    2023-12-12
  • golang flag簡(jiǎn)單用法

    golang flag簡(jiǎn)單用法

    本篇文章介紹了golang flag包的一個(gè)簡(jiǎn)單的用法,希望通過(guò)一個(gè)簡(jiǎn)單的實(shí)例,能讓大家了解它的用法,從中獲得啟發(fā)
    2018-09-09

最新評(píng)論