<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Golang 中不要犯這 5 個(gè)錯(cuò)誤

          共 2170字,需瀏覽 5分鐘

           ·

          2021-11-05 15:12

          Go 給人的印象是容易入門,因?yàn)檎Z法簡(jiǎn)單。不過新手還是比較容易犯一些錯(cuò)誤的。

          本文總結(jié)了 5 個(gè)常見的錯(cuò)誤,你檢驗(yàn)下自己犯過沒有?!這些是我寫 Go 時(shí)所犯的錯(cuò)誤,希望對(duì)你有幫助!

          1、循環(huán)內(nèi)部

          在循環(huán)中,有幾種情況可能會(huì)導(dǎo)致混亂,你需要弄清楚。

          1.1、循環(huán)迭代器變量中使用引用

          出于效率考慮,經(jīng)常使用單個(gè)變量來循環(huán)迭代器。由于在每次循環(huán)迭代中會(huì)有不同的值,有些時(shí)候這會(huì)導(dǎo)致未知的行為。例如:

          in?:=?[]int{1,?2,?3}

          var?out?[]*int
          for??_,?v?:=?range?in?{
          ?out?=?append(out,?&v)
          }

          fmt.Println("Values:",?*out[0],?*out[1],?*out[2])
          fmt.Println("Addresses:",?out[0],?out[1],?out[2])

          輸出結(jié)果:

          Values:?3?3?3
          Addresses:?0xc000014188?0xc000014188?0xc000014188

          是不是很驚訝?在 out 這個(gè) slice 中的元素都是 3。實(shí)際上很容易解釋為什么會(huì)這樣:在每次迭代中,我們都將 v append 到 out 切片中。因?yàn)?v 是單個(gè)變量(內(nèi)存地址不變),每次迭代都采用新值。在輸出的第二行證明了地址是相同的,并且它們都指向相同的值。

          簡(jiǎn)單的解決方法是將循環(huán)迭代器變量復(fù)制到新變量中:

          in?:=?[]int{1,?2,?3}

          var?out?[]*int
          for??_,?v?:=?range?in?{
          ?v?:=?v
          ?out?=?append(out,?&v)
          }

          fmt.Println("Values:",?*out[0],?*out[1],?*out[2])
          fmt.Println("Addresses:",?out[0],?out[1],?out[2])

          新的輸出:

          Values:?1?2?3
          Addresses:?0xc0000b6010?0xc0000b6018?0xc0000b6020

          在 goroutine 中使用循環(huán)迭代變量會(huì)有相同的問題。

          list?:=?[]int{1,?2,?3}

          for?_,?v?:=?range?list?{
          ?go?func()?{
          ??fmt.Printf("%d?",?v)
          ?}()
          }

          輸出將是:

          3?3?3

          可以使用上述完全相同的解決方案進(jìn)行修復(fù)。請(qǐng)注意,如果不使用 goroutine 運(yùn)行該函數(shù),則代碼將按預(yù)期運(yùn)行。

          這個(gè)錯(cuò)誤犯錯(cuò)率是很高的,要特別注意!!

          1.2、在循環(huán)中調(diào)用 WaitGroup.Wait

          看一段代碼:

          var?wg?sync.WaitGroup
          wg.Add(len(tasks))
          for?_,?t?:=?range?tasks?{
          ?go?func(t?*task)?{?
          ??defer?group.Done()
          ?}(t)
          ?//?group.Wait()
          }

          group.Wait()

          WaitGroup 常用來等待多個(gè) goroutine 運(yùn)行完成。但如果 Wait 在循環(huán)內(nèi)部調(diào)用,即代碼中第 7 行的位置,得到的結(jié)果就不是預(yù)期的了。這個(gè)錯(cuò)誤犯錯(cuò)率應(yīng)該比較低。

          1.3、循環(huán)內(nèi)使用 defer

          因?yàn)?defer 的執(zhí)行時(shí)機(jī)是函數(shù)返回前。所以,一般不應(yīng)該在循環(huán)內(nèi)部使用 defer,除非你很清楚自己在干什么。

          看一段代碼:

          var?mutex?sync.Mutex
          type?Person?struct?{
          ?Age?int
          }
          persons?:=?make([]Person,?10)
          for?_,?p?:=?range?persons?{
          ?mutex.Lock()
          ?//?defer?mutex.Unlock()
          ?p.Age?=?13
          ?mutex.Unlock()
          }

          在上面的示例中,如果使用第 8 行而不是第 10 行,則下一次迭代將無法獲得互斥鎖,因?yàn)樵撴i并沒有釋放,所以循環(huán)會(huì)永遠(yuǎn)阻塞。

          如果你確實(shí)需要在循環(huán)內(nèi)部使用 defer,則通過委托給另外一個(gè)函數(shù)的方式進(jìn)行:

          var?mutex?sync.Mutex
          type?Person?struct?{
          ?Age?int
          }
          persons?:=?make([]Person,?10)
          for?_,?p?:=?range?persons?{
          ?func()?{
          ??mutex.Lock()
          ??defer?mutex.Unlock()
          ??p.Age?=?13
          ?}()
          }

          2、channel 堵塞

          一般認(rèn)為 goroutine + channel 是 Go 的利器。Go 強(qiáng)調(diào)不要通過共享內(nèi)存來通訊,而是通過通訊來共享內(nèi)存。

          但在使用 channel 的過程中,需要注意堵塞問題,避免導(dǎo)致 goroutine 泄露。比如下面的代碼:

          func?doReq(timeout?time.Duration)?obj?{
          ?//?ch?:=?make(chan?obj)
          ?ch?:=?make(chan?obj,?1)
          ?go?func()?{
          ??obj?:=?do()
          ??ch?<-?result
          ?}?()
          ?select?{
          ?case?result?=?<-?ch:
          ??return?result
          ??case?<-?time.After(timeout):
          ??return?nil?
          ?}
          }

          檢查一下上面的代碼的 doReq 函數(shù),在第 4 行創(chuàng)建一個(gè)子 goroutine 來處理請(qǐng)求,這是 Go 服務(wù)器程序中的常見做法。

          子 goroutine 執(zhí)行 do 函數(shù)并通過第 6 行的通道 ch 將結(jié)果發(fā)送回父 goroutine。子 goroutine 將在第 6 行阻塞,直到父 goroutine 在第 9 行從 ch 接收到結(jié)果為止。同時(shí),父 goroutine 將在 select 阻塞,直到子 goroutine 將結(jié)果發(fā)送給 ch(第 9 行)或超時(shí)(第 11 行)。如果超時(shí)先發(fā)生,則父 goroutine 將從 doReq 第 12 行返回,這會(huì)導(dǎo)致沒有 goroutine 從 ch 讀取數(shù)據(jù),子 goroutine 就會(huì)一直堵塞在第 6 行。解決辦法是將 ch 從無緩沖的通道改為有緩沖的通道,因此子goroutine 即使在父 goroutine 退出后也始終可以發(fā)送結(jié)果。

          這個(gè)錯(cuò)誤出現(xiàn)概率不會(huì)低。還有特別要注意的一點(diǎn),就是 time.After 導(dǎo)致的內(nèi)存泄露問題,只要注意程序不是頻繁執(zhí)行上面的 select 即可(畢竟 time.After 到時(shí)間了還是會(huì)回收資源的)。

          3、不使用接口

          接口可以使代碼更靈活。這是在代碼中引入多態(tài)的一種方法。接口允許你定義一組行為而不是特定類型。不使用接口可能不會(huì)導(dǎo)致任何錯(cuò)誤,但是會(huì)導(dǎo)致代碼簡(jiǎn)單性,靈活性和擴(kuò)展性降低。

          在 Go 接口中,io.Reader 和 io.Writer 可能是使用最多的。

          type?Reader?interface?{
          ????Read(p?[]byte)?(n?int,?err?error)
          }
          type?Writer?interface?{
          ????Write(p?[]byte)?(n?int,?err?error)
          }

          這些接口非常強(qiáng)大,假設(shè)你要將對(duì)象寫入文件,你可以定義了一個(gè) Save 方法:

          func?(o?*obj)?Save(file?os.File)?error

          如果第二天,你想寫入 http.ResponseWriter,顯然不太適合再創(chuàng)建另外一個(gè) Save 方法,這時(shí)應(yīng)該用 io.Writer:

          func?(o?*obj)?Save(w?io.Writer)?error

          另外,你應(yīng)該知道的重要注意事項(xiàng)是,始終關(guān)注行為。在上面的示例中,雖然 io.ReadWriteCloser 也可以使用,但你只需要 Write 方法。接口越大,抽象性越弱。在 Go 中,通常提倡小接口。

          所以,我們應(yīng)該優(yōu)先考慮使用接口,而不是具體類型。

          4、不注意結(jié)構(gòu)體字段順序

          這個(gè)問題不會(huì)導(dǎo)致程序錯(cuò)誤,但是可能會(huì)占用更多內(nèi)存。

          看一個(gè)例子:

          type?BadOrderedPerson?struct?{
          ?Veteran?bool???//?1?byte
          ?Name????string?//?16?byte
          ?Age?????int32??//?4?byte
          }

          type?OrderedPerson?struct?{
          ?Name????string
          ?Age?????int32
          ?Veteran?bool
          }

          看起來這兩個(gè)類型都占用的空間都是 21字節(jié),但是結(jié)果卻不是這樣。我們使用 GOARCH=amd64 編譯代碼,發(fā)現(xiàn) BadOrderedPerson 類型占用 32 個(gè)字節(jié),而 ?OrderedPerson 類型只占用 24 個(gè)字節(jié)。為什么?原因是數(shù)據(jù)結(jié)構(gòu)對(duì)齊[1]。在 64 位體系結(jié)構(gòu)中,內(nèi)存分配連續(xù)的 8 字節(jié)數(shù)據(jù)。需要添加的填充可以通過以下方式計(jì)算:

          padding?=?(align?-?(offset?mod?align))?mod?align
          aligned?=?offset?+?padding
          ????????=?offset?+?((align?-?(offset?mod?align))?mod?align)
          type?BadOrderedPerson?struct?{
          ?Veteran?bool?????//?1?byte
          ?_???????[7]byte??//?7?byte:?padding?for?alignment
          ?Name????string???//?16?byte
          ?Age?????int32????//?4?byte
          ?_???????struct{}?//?to?prevent?unkeyed?literals
          ?//?zero?sized?values,?like?struct{}?and?[0]byte?occurring?at?
          ?//?the?end?of?a?structure?are?assumed?to?have?a?size?of?one?byte.
          ?//?so?padding?also?will?be?addedd?here?as?well.
          ?
          }

          type?OrderedPerson?struct?{
          ?Name????string
          ?Age?????int32
          ?Veteran?bool
          ?_???????struct{}?
          }

          當(dāng)你使用大型常用類型時(shí),可能會(huì)導(dǎo)致性能問題。但是不用擔(dān)心,你不必手動(dòng)處理所有結(jié)構(gòu)。這工具可以輕松的解決此類問題:https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/fieldalignment。

          5、測(cè)試中不使用 race 探測(cè)器

          數(shù)據(jù)爭(zhēng)用會(huì)導(dǎo)致莫名的故障,通常是在代碼已部署到線上很久之后才出現(xiàn)。因此,它們是并發(fā)系統(tǒng)中最常見且最難調(diào)試的錯(cuò)誤類型。為了幫助區(qū)分此類錯(cuò)誤,Go 1.1 引入了內(nèi)置的數(shù)據(jù)爭(zhēng)用檢測(cè)器(race detector)。可以簡(jiǎn)單地添加 -race flag 來使用。

          $?go?test?-race?pkg????#?to?test?the?package
          $?go?run?-race?pkg.go??#?to?run?the?source?file
          $?go?build?-race???????#?to?build?the?package
          $?go?install?-race?pkg?#?to?install?the?package

          啟用數(shù)據(jù)爭(zhēng)用檢測(cè)器后,編譯器將記錄在代碼中何時(shí)以及如何訪問內(nèi)存,而 ?runtime 監(jiān)控對(duì)共享變量的非同步訪問。

          找到數(shù)據(jù)競(jìng)爭(zhēng)后,競(jìng)爭(zhēng)檢測(cè)器將打印一份報(bào)告,其中包含用于沖突訪問的堆棧跟蹤。這是一個(gè)例子:

          WARNING:?DATA?RACE
          Read?by?goroutine?185:
          ??net.(*pollServer).AddFD()
          ??????src/net/fd_unix.go:89?+0x398
          ??net.(*pollServer).WaitWrite()
          ??????src/net/fd_unix.go:247?+0x45
          ??net.(*netFD).Write()
          ??????src/net/fd_unix.go:540?+0x4d4
          ??net.(*conn).Write()
          ??????src/net/net.go:129?+0x101
          ??net.func·060()
          ??????src/net/timeout_test.go:603?+0xaf

          Previous?write?by?goroutine?184:
          ??net.setWriteDeadline()
          ??????src/net/sockopt_posix.go:135?+0xdf
          ??net.setDeadline()
          ??????src/net/sockopt_posix.go:144?+0x9c
          ??net.(*conn).SetDeadline()
          ??????src/net/net.go:161?+0xe3
          ??net.func·061()
          ??????src/net/timeout_test.go:616?+0x3ed

          Goroutine?185?(running)?created?at:
          ??net.func·061()
          ??????src/net/timeout_test.go:609?+0x288

          Goroutine?184?(running)?created?at:
          ??net.TestProlongTimeout()
          ??????src/net/timeout_test.go:618?+0x298
          ??testing.tRunner()
          ??????src/testing/testing.go:301?+0xe8

          總結(jié)

          錯(cuò)誤不可怕,但我們需要從錯(cuò)誤中吸取教訓(xùn),避免再次掉入同樣的坑里。掉入一個(gè)坑, 我們應(yīng)該想辦法探究出原因,知道為什么,下次再掉坑的可能性就會(huì)小很多。

          除了以上這幾點(diǎn),還有哪些你常碰到的錯(cuò)誤或坑呢?歡迎交流!

          原文地址:https://hackernoon.com/dont-make-these-5-golang-mistakes-3l3x3wcw

          參考資料

          [1]

          數(shù)據(jù)結(jié)構(gòu)對(duì)齊: https://en.wikipedia.org/wiki/Data_structure_alignment




          往期推薦


          我是 polarisxu,北大碩士畢業(yè),曾在 360 等知名互聯(lián)網(wǎng)公司工作,10多年技術(shù)研發(fā)與架構(gòu)經(jīng)驗(yàn)!2012 年接觸 Go 語言并創(chuàng)建了 Go 語言中文網(wǎng)!著有《Go語言編程之旅》、開源圖書《Go語言標(biāo)準(zhǔn)庫》等。


          堅(jiān)持輸出技術(shù)(包括 Go、Rust 等技術(shù))、職場(chǎng)心得和創(chuàng)業(yè)感悟!歡迎關(guān)注「polarisxu」一起成長(zhǎng)!也歡迎加我微信好友交流:gopherstudio

          瀏覽 37
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产成人综合欧美精品久久 | 天天肏夜夜肏 | PORNY九色视频9l自拍 | 日本熟妇在线 | 五月天色婷婷综合 |