<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>

          5個(gè)golang中易犯的錯(cuò)誤

          共 2131字,需瀏覽 5分鐘

           ·

          2021-02-15 22:34

          To err is human,to forgive divine.

          ? ? ? ? ? ? ? ? -Alexander Pope

          初學(xué)golang我們經(jīng)常會(huì)犯一些錯(cuò)誤,雖然它們不會(huì)產(chǎn)生類型檢查的異常,但是它們往往潛在影響軟件的功能。

          01


          循環(huán)中易犯的錯(cuò)誤



          1.1

          使用循環(huán)迭代變量的指針

          先來看一段代碼
          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 3Addresses:?0xc0000a4008?0xc0000a4008?0xc0000a4008
          你可能會(huì)很奇怪為什么會(huì)出現(xiàn)這種情況,結(jié)果不應(yīng)該是? 1 2 3 和三個(gè)不同的地址嗎?其實(shí)真實(shí)原因for range過程中創(chuàng)建了每個(gè)元素的副本,而不是直接返回每個(gè)元素的引用。v在for循環(huán)引進(jìn)的一個(gè)塊作用域內(nèi)進(jìn)行聲明,它是一個(gè)共享的可訪問的地址。在迭代過程中,返回的變量是根據(jù)切片依次賦值的到變量v中,故而值的地址總是相同的,導(dǎo)致結(jié)果不如預(yù)期。那么該如何修改呢?
          最簡(jiǎn)單的做法是將循環(huán)迭代變量復(fù)制到新的變量中:
          in := []int{1, 2, 3}
          var out []*intfor _, 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])

          PS:也可以直接根據(jù)range返回第一個(gè)參數(shù)作為數(shù)組索引下標(biāo) 拿值

          循環(huán)中g(shù)oroutine使用循環(huán)迭代變量也會(huì)存在同樣的問題:

          list := []int{1, 2, 3}
          for _, v := range list { go func() { fmt.Printf("%d ", v) }()}

          輸出結(jié)果:

          3 3 3




          1.2

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

          按照WaitGroup的正常用法,當(dāng)wg.Done()被調(diào)用len(tasks)次,wg.Wait()會(huì)被自動(dòng)解除阻塞。當(dāng)時(shí)下面代碼中,將wg.wait()放到循環(huán)中后,導(dǎo)致第二次循環(huán)被阻塞,解決辦法 將wg.wait()移除循環(huán)即可。


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




          1.3

          循環(huán)中使用defer

          defer是在函數(shù)返回的時(shí)候才執(zhí)行,除非我們知道自己在做什么否則你不應(yīng)該在循環(huán)中使用defer
          var mutex sync.Mutextype Person struct {  Age int}persons := make([]Person, 10)for _, p := range persons {  mutex.Lock()  // defer mutex.Unlock()  p.Age = 13  mutex.Unlock()}
          上面的例子中,如果使用第8行代替第10行代碼,則下一次循環(huán)將因?yàn)闊o法獲取排他鎖永遠(yuǎn)被阻塞。
          如果你真的想在內(nèi)循環(huán)中使用defer,你很可能是想委托其他函數(shù)來完成任務(wù)。
          var mutex sync.Mutextype Person struct {  Age int}persons := make([]Person, 10)for _, p := range persons {  func() {    mutex.Lock()    defer mutex.Unlock()    p.Age = 13  }()}
          但是,有時(shí)在循環(huán)中使用defer確實(shí)比較方便,但是你真的應(yīng)該知道你在做什么。Go不能容忍愚蠢的人。

          02


          發(fā)送到一個(gè)無保證的channel


          我們可以在一個(gè)goroutine中發(fā)送數(shù)據(jù)到channels,在另一個(gè)goroutine中接收這些數(shù)據(jù)。默認(rèn)情況下,發(fā)送和接收會(huì)阻塞直到對(duì)方ready。這使得goroutines可以不用顯式使用鎖或條件變量就可以完成同步操作。
          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ù)創(chuàng)建了一個(gè)子Goroutine來處理請(qǐng)求,這在go服務(wù)端程序中是常見的做法。子Goroutine執(zhí)行do函數(shù)并通過channel發(fā)送結(jié)果給父節(jié)點(diǎn)。子Goroutine將會(huì)阻塞直到父節(jié)點(diǎn)從channel中收到數(shù)據(jù)。與此同時(shí),父節(jié)點(diǎn)也會(huì)阻塞在select上,直到子Goroutine發(fā)送結(jié)果到channel,或者超時(shí)。當(dāng)超時(shí)先發(fā)生,則會(huì)執(zhí)行第12行代碼并且子Goroutine將永遠(yuǎn)阻塞。
          解決方案:
          • 將ch從無緩沖channel改成有緩沖channel,這樣子Goroutine將永遠(yuǎn)可以發(fā)送結(jié)果數(shù)據(jù),即使父節(jié)點(diǎn)已經(jīng)退出

          • select中使用default語句,如果沒有g(shù)oroutine收到ch,則會(huì)發(fā)送默認(rèn)情況。盡管這種方案不是總能生效。

          ...select { case ch <- result: default:}...

          03


          不使用接口


          接口的使用可以讓我們的代碼更加靈活,也是一種在代碼中引入多態(tài)的方法。接口允許我們請(qǐng)求一組行為而不是特定類型。不使用接口不會(huì)產(chǎn)生任何錯(cuò)誤,但是它會(huì)導(dǎo)致我們的代碼不簡(jiǎn)潔、不靈活、并且不具備可拓展性。
          眾多接口中,io.Readerio.Writer可能是最受歡迎的。
          type Reader interface {    Read(p []byte) (n int, err error)}type Writer interface {    Write(p []byte) (n int, err error)}
          這些接口功能非常強(qiáng)大。假設(shè)你要向一個(gè)文件中寫入數(shù)據(jù),你會(huì)定義一個(gè)save方法:
          func?(o?*obj)Save(file?os.File) error
          但是第二天你又想往http.ResponseWriter中寫入數(shù)據(jù),但是你不想再定義一個(gè)新的方法,怎么辦?使用io.Writer
          func?(o?*obj)Save(w io.Writer) error
          還有一個(gè)重點(diǎn)注意的事項(xiàng),你應(yīng)該知道總是請(qǐng)求你要使用的行為。上面的例子中,請(qǐng)求一個(gè)io.ReadWriteCloser也可以正常工作,但它不是一個(gè)最佳實(shí)踐,因?yàn)槲覀冎皇窍胧褂靡粋€(gè)Write方法。接口越大抽象越弱,所以絕大多時(shí)候最好使用行為而不是具體的類型。

          04


          糟糕的結(jié)構(gòu)體字段排序

          糟糕順序的結(jié)構(gòu)體雖然也不會(huì)導(dǎo)致任何錯(cuò)誤,但是它會(huì)造成更多的內(nèi)存消耗。
          type BadOrderedPerson struct {  Veteran bool   // 1 byte  Name    string // 16 byte  Age     int32  // 4 byte}
          type OrderedPerson struct { Name string Age int32 Veteran bool}
          上面代碼看起來兩種類型都占用了相同的21bytes的內(nèi)存空間,但是結(jié)果顯示卻完全不同。我們使用GOARCH=amd64來編譯代碼:
          • BadOrderedPerson 類型分配了32bytes

          • OrderedPerson類型分配了24bytes

          為什么會(huì)這樣呢?原因是數(shù)據(jù)結(jié)構(gòu)對(duì)齊。在64位架構(gòu)中,內(nèi)存分配8字節(jié)的連續(xù)數(shù)據(jù)包。需要添加的填充可以通過下面的公式計(jì)算得出:
          padding = (align - (offset mod align)) mod alignaligned = 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)我們高頻使用一個(gè)大的糟糕排序的結(jié)構(gòu)體類型,會(huì)導(dǎo)致性能問題。但是不用擔(dān)心,我們不用人肉檢查結(jié)構(gòu)體順序定義問題,使用
          maligned(https://github.com/mdempsky/maligned)
          可以輕松檢查此類問題。

          05


          測(cè)試中不使用race detector

          數(shù)據(jù)競(jìng)爭(zhēng)會(huì)引發(fā)神秘的錯(cuò)誤,經(jīng)常發(fā)生在我們代碼部署線上部署很長一段時(shí)間后。正是這個(gè)原因,它也是并發(fā)系統(tǒng)中最常見也是最難調(diào)試的問題。為了幫助區(qū)分這類bug,Go1.1引入了一個(gè)內(nèi)置的數(shù)據(jù)競(jìng)爭(zhēng)檢測(cè)器。使用過程只需要簡(jiǎn)單的添加一個(gè)-race?標(biāo)志即可。
          $ 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
          啟用race后,編譯器會(huì)記錄代碼訪問內(nèi)存的時(shí)間和方式,而runtime監(jiān)視共享變量的非同步訪問。
          當(dāng)數(shù)據(jù)競(jìng)爭(zhēng)被檢測(cè)到,競(jìng)爭(zhēng)檢測(cè)器會(huì)打印一份報(bào)告,包括沖突訪問的堆棧跟蹤信息。一下是一個(gè)栗子:
          WARNING: DATA RACERead 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 +0xafPrevious 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 +0x3edGoroutine 185 (running) created at:  net.func·061()      src/net/timeout_test.go:609 +0x288Goroutine 184 (running) created at:  net.TestProlongTimeout()      src/net/timeout_test.go:618 +0x298  testing.tRunner()      src/testing/testing.go:301 +0xe8


          推薦閱讀


          福利

          我為大家整理了一份從入門到進(jìn)階的Go學(xué)習(xí)資料禮包,包含學(xué)習(xí)建議:入門看什么,進(jìn)階看什么。關(guān)注公眾號(hào) 「polarisxu」,回復(fù)?ebook?獲取;還可以回復(fù)「進(jìn)群」,和數(shù)萬 Gopher 交流學(xué)習(xí)。



          瀏覽 16
          點(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>
                  丁香深爱五月 | 久久精品国产亚洲7777 | www.色婷婷五月综合在线色吧 | 日韩一级免费在线观看 | 久久婷婷AV |