自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

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

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

[[431845]]

大家好,我是程序員幽鬼。

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

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

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

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

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

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

  1. in := []int{1, 2, 3} 
  2.  
  3. var out []*int 
  4. for  _, v := range in { 
  5.  out = append(out, &v) 
  6.  
  7. fmt.Println("Values:", *out[0], *out[1], *out[2]) 
  8. fmt.Println("Addresses:"out[0], out[1], out[2]) 

輸出結(jié)果:

  1. Values: 3 3 3 
  2. Addresses: 0xc000014188 0xc000014188 0xc000014188 

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

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

  1. in := []int{1, 2, 3} 
  2.  
  3. var out []*int 
  4. for  _, v := range in { 
  5.  v := v 
  6.  out = append(out, &v) 
  7.  
  8. fmt.Println("Values:", *out[0], *out[1], *out[2]) 
  9. fmt.Println("Addresses:"out[0], out[1], out[2]) 

新的輸出:

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

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

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

輸出將是:

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

看一段代碼:

  1. var wg sync.WaitGroup 
  2. wg.Add(len(tasks)) 
  3. for _, t := range tasks { 
  4.  go func(t *task) {  
  5.   defer group.Done() 
  6.  }(t) 
  7.  // group.Wait() 
  8.  
  9. group.Wait() 

WaitGroup 常用來(lái)等待多個(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,除非你很清楚自己在干什么。

看一段代碼:

  1. var mutex sync.Mutex 
  2. type Person struct { 
  3.  Age int 
  4. persons := make([]Person, 10) 
  5. for _, p := range persons { 
  6.  mutex.Lock() 
  7.  // defer mutex.Unlock() 
  8.  p.Age = 13 
  9.  mutex.Unlock() 

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

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

  1. var mutex sync.Mutex 
  2. type Person struct { 
  3.  Age int 
  4. persons := make([]Person, 10) 
  5. for _, p := range persons { 
  6.  func() { 
  7.   mutex.Lock() 
  8.   defer mutex.Unlock() 
  9.   p.Age = 13 
  10.  }() 

2、channel 堵塞

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

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

  1. func doReq(timeout time.Duration) obj { 
  2.  // ch := make(chan obj) 
  3.  ch := make(chan obj, 1) 
  4.  go func() { 
  5.   obj := do() 
  6.   ch <- result 
  7.  } () 
  8.  select { 
  9.  case result = <- ch: 
  10.   return result 
  11.   case <- time.After(timeout): 
  12.   return nil  
  13.  } 

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

子 goroutine 執(zhí)行 do 函數(shù)并通過(guò)第 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)致沒(méi)有 goroutine 從 ch 讀取數(shù)據(jù),子 goroutine 就會(huì)一直堵塞在第 6 行。解決辦法是將 ch 從無(wú)緩沖的通道改為有緩沖的通道,因此子goroutine 即使在父 goroutine 退出后也始終可以發(fā)送結(jié)果。

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

3、不使用接口

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

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

  1. type Reader interface { 
  2.     Read(p []byte) (n int, err error) 
  3. type Writer interface { 
  4.     Write(p []byte) (n int, err error) 

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

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

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

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

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

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

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

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

看一個(gè)例子:

  1. type BadOrderedPerson struct { 
  2.  Veteran bool   // 1 byte 
  3.  Name    string // 16 byte 
  4.  Age     int32  // 4 byte 
  5.  
  6. type OrderedPerson struct { 
  7.  Name    string 
  8.  Age     int32 
  9.  Veteran bool 

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

  1. padding = (align - (offset mod align)) mod align 
  2. aligned = offset + padding 
  3.         = offset + ((align - (offset mod align)) mod align) 
  4. type BadOrderedPerson struct { 
  5.  Veteran bool     // 1 byte 
  6.  _       [7]byte  // 7 byte: padding for alignment 
  7.  Name    string   // 16 byte 
  8.  Age     int32    // 4 byte 
  9.  _       struct{} // to prevent unkeyed literals 
  10.  // zero sized valueslike struct{} and [0]byte occurring at  
  11.  // the end of a structure are assumed to have a size of one byte. 
  12.  // so padding also will be addedd here as well. 
  13.   
  14.  
  15. type OrderedPerson struct { 
  16.  Name    string 
  17.  Age     int32 
  18.  Veteran bool 
  19.  _       struct{}  

當(dāng)你使用大型常用類(lèi)型時(shí),可能會(huì)導(dǎo)致性能問(wèn)題。但是不用擔(dān)心,你不必手動(dòng)處理所有結(jié)構(gòu)。這工具可以輕松的解決此類(lèi)問(wèn)題: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)中最常見(jiàn)且最難調(diào)試的錯(cuò)誤類(lèi)型。為了幫助區(qū)分此類(lèi)錯(cuò)誤,Go 1.1 引入了內(nèi)置的數(shù)據(jù)爭(zhēng)用檢測(cè)器(race detector)??梢院?jiǎn)單地添加 -race flag 來(lái)使用。

  1. $ go test -race pkg    # to test the package 
  2. $ go run -race pkg.go  # to run the source file 
  3. $ go build -race       # to build the package 
  4. $ go install -race pkg # to install the package 

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

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

  1. WARNING: DATA RACE 
  2. Read by goroutine 185: 
  3.   net.(*pollServer).AddFD() 
  4.       src/net/fd_unix.go:89 +0x398 
  5.   net.(*pollServer).WaitWrite() 
  6.       src/net/fd_unix.go:247 +0x45 
  7.   net.(*netFD).Write() 
  8.       src/net/fd_unix.go:540 +0x4d4 
  9.   net.(*conn).Write() 
  10.       src/net/net.go:129 +0x101 
  11.   net.func·060() 
  12.       src/net/timeout_test.go:603 +0xaf 
  13.  
  14. Previous write by goroutine 184: 
  15.   net.setWriteDeadline() 
  16.       src/net/sockopt_posix.go:135 +0xdf 
  17.   net.setDeadline() 
  18.       src/net/sockopt_posix.go:144 +0x9c 
  19.   net.(*conn).SetDeadline() 
  20.       src/net/net.go:161 +0xe3 
  21.   net.func·061() 
  22.       src/net/timeout_test.go:616 +0x3ed 
  23.  
  24. Goroutine 185 (running) created at
  25.   net.func·061() 
  26.       src/net/timeout_test.go:609 +0x288 
  27.  
  28. Goroutine 184 (running) created at
  29.   net.TestProlongTimeout() 
  30.       src/net/timeout_test.go:618 +0x298 
  31.   testing.tRunner() 
  32.       src/testing/testing.go:301 +0xe8 

總結(jié)

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

原文地址: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

 

責(zé)任編輯:武曉燕 來(lái)源: 幽鬼
相關(guān)推薦

2018-03-15 10:21:50

程序員面試低級(jí)錯(cuò)誤

2019-07-08 13:58:03

Java數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)

2010-04-20 14:59:56

面試

2017-10-18 10:37:07

VMware虛擬化架構(gòu)

2019-08-02 16:15:13

2020-07-26 00:40:48

JavaScript開(kāi)發(fā)代碼

2020-03-31 22:09:01

React應(yīng)用程序庫(kù)

2021-02-06 10:27:51

Linux發(fā)行版操作系統(tǒng)

2021-02-03 13:03:00

編程程序員語(yǔ)言

2023-10-28 16:30:19

Golang開(kāi)發(fā)

2023-11-30 07:15:36

GolangRecover

2021-12-16 06:52:33

Ceph分布式對(duì)象

2020-08-17 17:22:34

VSCode插件開(kāi)發(fā)編碼

2019-11-28 18:51:07

PythonPHP編程語(yǔ)言

2020-11-20 10:30:48

云計(jì)算SaaS技術(shù)

2021-08-06 09:20:41

IT管理IT領(lǐng)導(dǎo)者CIO

2015-06-08 10:31:30

程序員代碼

2016-05-04 09:45:01

CSS開(kāi)發(fā)不要

2019-08-01 12:36:54

物聯(lián)網(wǎng)項(xiàng)目物聯(lián)網(wǎng)IOT

2019-11-29 10:03:43

5G技術(shù)智能家居
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)