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

Go語言中常見的坑以及高性能編程技巧分享

 更新時間:2023年06月28日 11:17:39   作者:螞蟻的感知  
代碼的穩(wěn)健性、高性能、可讀性是我們每一位coder必須去追求的目標,本文結(jié)合Go語言的特性做了相關(guān)總結(jié),感興趣的小伙伴可以了解一下

背景

代碼的穩(wěn)健性、高性能、可讀性是我們每一位coder必須去追求的目標,也是coding的基本功。

本文結(jié)合Go語言的特性,以及自己在寫Go項目中做的總結(jié),從Go常見的數(shù)據(jù)結(jié)構(gòu)、內(nèi)存管理、并發(fā)等方面做了相關(guān)總結(jié)

本文相關(guān)代碼的驗證環(huán)境

GOARCH="arm64" GOOS="darwin" GOVERSION="go1.16.15"

一. 常見的坑

1.1 數(shù)據(jù)結(jié)構(gòu)

1.在函數(shù)調(diào)用過程中,數(shù)組是值傳遞pass-by-value,必要時必須使用slice。因為slice是引用類型,在slice傳參時,只會復(fù)制slice的Data指針和len、cap,形參和實參的slice使用的是同一個底層數(shù)組。

2.map是一種hash表實現(xiàn),每次遍歷的順序都可能不一樣。

3.切片會導(dǎo)致整個數(shù)組被鎖定,導(dǎo)致底層數(shù)組無法及時釋放內(nèi)存,如果底層數(shù)組過大,會對內(nèi)存產(chǎn)生極大的壓力。

錯誤示例:

func test() {
    fileHeaderMap := make(map[string][]byte)
    for i := 0; i < 5; i++ {
        name := "/data/test/file_"+strconv.Itoa(i)
        data, err := ioutil.ReadFile(name)
        if err != nil {
            fmt.Println(err)
            continue
        }
        fileHeaderMap[name] = data[:1]
   }
   // do some thing
}

正確示例:

解決辦法是將結(jié)果克隆一份,這樣可以釋放底層數(shù)組

func test() {
    fileHeaderMap := make(map[string][]byte)
    for i := 0; i < 5; i++ {
        name := "/data/test/file_"+strconv.Itoa(i)
        data, err := ioutil.ReadFile(name)
        if err != nil {
            fmt.Println(err)
            continue
        }
        fileHeaderMap[name] = append([]byte{}, data[:1]...)
    }
    // do some thing
}

4.當函數(shù)的可變參數(shù)是空接口時,傳入空接口的切片時需要注意參數(shù)展開的問題

func main() {
    var a = []interface{}{111, 222, 333}
    fmt.Println(a)
    fmt.Println(a...)
}
// 不管是否展開,編譯器都會編譯通過,但是輸出是不同的:
//print:
[111 222 333]
111 222 333

5.對于切片的操作,會操作同一個底層數(shù)組,因此對于一個切片的修改操作,會影響到整個數(shù)組。另外由于string 在go中是immutable,對于同一個字符串的兩個變量,Go做了內(nèi)存優(yōu)化,會使用的相同的底層數(shù)組

func main() {
    slice := []int{1, 2, 3, 4, 5}
    slice1 := slice[:2]
    slice2 := slice[:4]
    fmt.Println("slice: ", slice, ",slice1: ", slice1, ",slice2: ", slice2)
    slice2[0] = 6
    fmt.Println("slice: ", slice, ",slice1: ", slice1, ",slice2: ", slice2)
    string1 := "go hello word"
    string2 := "go hello word"
    fmt.Printf("string1 data addr: %d \n", (*reflect.StringHeader)(unsafe.Pointer(&string1)).Data)
    fmt.Printf("string2 data addr: %d \n", (*reflect.StringHeader)(unsafe.Pointer(&string2)).Data)
}
//print:
slice:  [1 2 3 4 5] ,slice1:  [1 2] ,slice2:  [1 2 3 4]
slice:  [6 2 3 4 5] ,slice1:  [6 2] ,slice2:  [6 2 3 4]
string1 data addr: 4333475958 
string2 data addr: 4333475958

1.2 Go語言特性相關(guān)

1.Go中的recover捕獲的是祖父級調(diào)用時候的panic,直接調(diào)用時不會有任何效果,必須在defer函數(shù)中調(diào)用才有效果。

func test() err error  {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("[ERROR] Process exception. Panic: %v", r)
        }
    }()
    // do some thing
    panic("some error happend")
}

2.不同goroutine之間不滿足順序一致性的內(nèi)存模型,需要使用顯示的同步:如 channel

var testMsg string
var done = make(chan struct{})
func initMsg() {
    testMsg = "go hello world"
    done <- struct{}{}
}
func main() {
    go initMsg()
    <-done
    println(testMsg)
}

3.閉包的錯誤使用,導(dǎo)致使用同一個變量

錯誤示例:

func main() {
    for i := 0; i < 5; i++ {
        defer func() {
            println(i)
        }()
    }
}
//print:
5
5
5
5
5

正確示例:

func main() {
    for i := 0; i < 5; i++ {
        defer func(i int) {
            println(i)
        }(i)
        // or
        //i := i
        //defer func(){
        //    println(i)
        //}()
    }
}
//print:
4
3
2
1
0

4.因為defer在函數(shù)退出時才會執(zhí)行,因此不能在循環(huán)內(nèi)部執(zhí)行defer,否則會導(dǎo)致相關(guān)defer操作調(diào)用延遲,比如fd會延遲關(guān)閉,應(yīng)將defer包裝在func中

錯誤示例:

func test() {
    for i := 0; i < 5; i++ {
        f, err := os.Open("/data/test/file_"+strconv.Itoa(i))
        if err != nil {
            fmt.Println(err)
            continue
        }
        defer f.Close()
        // do some thing
    }
    // do some thing
}

正確示例:

func test() {
    for i := 0; i < 5; i++ {
        func() {
            f, err := os.Open("/data/test/file_"+strconv.Itoa(i))
            if err != nil {
                fmt.Println(err)
                return
            }
            defer f.Close()
            // do some thing
        }()
    }
    // do some thing
}

5.Goroutine泄漏:

Go語言是帶內(nèi)存自動回收的特性,因此內(nèi)存一般不會泄漏。但是Goroutine卻存在泄漏的情況,同時泄漏的Goroutine引用的內(nèi)存也同樣無法被回收。因此可通過context包或者退出的channel來避免Goroutine的泄漏。

錯誤示例:

func main() {
    ch := func() <-chan int {
        ch := make(chan int)
        go func() {
            for i := 0; ; i++ {
                ch <- i
            }
        } ()
        return ch
    }()
    for value := range ch {
        fmt.Println(value)
        if value == 10 {
            break
        }
    }
    // do some thing
}

上面的程序中后臺Goroutine向channel輸入整數(shù),main函數(shù)中輸出該整數(shù)。但是當值為10時, break跳出for循環(huán),后臺Goroutine就處于無法被回收的狀態(tài)了

正確示例:

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    // stopChan := make(chan struct{})
    //ch := func(stopChan chan struct{}) <-chan int {
    ch := func(ctx context.Context) <-chan int {
        ch := make(chan int)
        go func() {
            for i := 0; ; i++ {
                select {
                //case <-stopChan:
                //   return
                case <- ctx.Done():
                    return
                case ch <- i:
                }
            }
        }()
        return ch
    }(ctx)
    //}(stopChan)
    for value := range ch {
        fmt.Println(value)
        if value == 10 {
            cancel()  //通過調(diào)用cancel()來通知后臺Goroutine退出
            // stopChan <- struct{}{}
            break
        }
    }
    // do some thing
}

6. go channel注意事項

  • 如果是一個buffer channel,即使被 close, 也可以讀到之前存入的值,讀取完畢后開始讀零值,已經(jīng)關(guān)閉的channel寫入則會觸發(fā) panic
  • nil channel 讀取和存入都會阻塞,close 會 panic
  • 已經(jīng)close過的channel再次close會觸發(fā)panic
  • 關(guān)閉channel的原則:不要在消費端進行關(guān)閉、不要在有多個并行的生產(chǎn)端關(guān)閉

二. 高性能Go編程

2.1 數(shù)據(jù)結(jié)構(gòu)

盡可能指定容器容量,以便為容器預(yù)先分配內(nèi)存。這將在后續(xù)添加元素時減少通過復(fù)制來調(diào)整容器大小

遍歷 []struct{} 使用下標而不是 range

如果切片是[]int,切片的Item為int差別不大,但是如果切片Item是一個結(jié)構(gòu)體類型struct{}時,且Item中包含一些比較大內(nèi)存的成員類型,比如 [2048]byte,如果每次遍歷[]struct{} ,都會進行一次值拷貝,所以會帶來性能消耗。此外,因為 range 遍歷事獲取值拷貝的副本,所以對副本的修改,是不會影響到原切片。 如果切片的Item是指針類型,即[]*struct{} 則兩者遍歷方法都一樣

string轉(zhuǎn)[]byte的零拷貝操作:

我們一般在做string轉(zhuǎn)[]byte時,會直接進行[]byte(string),這樣的話會發(fā)生一次拷貝操作,對于一些長尾的字符串,會產(chǎn)生性能問題。這是因為在Go的設(shè)計中string是immutable,而[]byte是mutable,如果不希望進行這次拷貝的消耗,這時候就需要用到unsafe

可參見:pkg.go.dev/reflect#SliceHeader

 func string2bytes(s string) []byte {
     stringHeader := (*reflect.StringHeader)(unsafe.Pointer(&s))
     var b []byte
     rs := (*reflect.SliceHeader)(unsafe.Pointer(&b))
     rs.Data = stringHeader.Data
     rs.Len = stringHeader.Len
     rs.Cap = stringHeader.Len
     return b
}

不過需要特別注意這樣生成的[]byte不可修改,否則會出現(xiàn)未定義的行為

Go 中空結(jié)構(gòu)體 struct{} 是不占用內(nèi)存空間,因此fmt.Println(unsafe.Sizeof(struct{}{})) 為0,不像 C/C++ 中空結(jié)構(gòu)體仍占用 1 字節(jié)大小,因此可用于以下幾個場景來節(jié)省內(nèi)存:

  • 在實現(xiàn)集合時,可以將map的value的設(shè)置為struct{}來節(jié)省內(nèi)存;
  • 在使用不發(fā)送數(shù)據(jù)的channel時,只是用于通知其他Goroutine,也可以使用空結(jié)構(gòu)體;
  • 僅包含方法的結(jié)構(gòu)體,即結(jié)構(gòu)體沒有任何成員類型字段

盡量少使用反射,因為反射里邊牽扯到類型判斷和內(nèi)存分配,會對性能產(chǎn)生影響

2.2 內(nèi)存管理

struct結(jié)構(gòu)體內(nèi)的成員布局需要考慮內(nèi)存對齊,一般建議字段寬度從小到大由上到下排列,減少內(nèi)存占用,提高內(nèi)存讀寫性能

值傳遞會拷貝整個對象,而指針傳遞只會拷貝對象的地址,指針指向的對象是同一個。返回指針可以減少值的拷貝,但是會導(dǎo)致內(nèi)存分配逃逸到堆中,即變量逃逸,增加垃圾回收(GC)的負擔。在對象頻繁創(chuàng)建和刪除的場景下,傳遞指針會導(dǎo)致GC的開銷增大,影響性能。

一般情況下,對于需要修改原對象值,或占用內(nèi)存比較大的結(jié)構(gòu)體,選擇返回指針。對于只讀的或者占用內(nèi)存較小的結(jié)構(gòu)體,直接返回值性能要優(yōu)于指針

對于需要重復(fù)分配、回收內(nèi)存的地方,優(yōu)先使用sync.Pool進行池化,用來保存和復(fù)用對象,減少內(nèi)存分配,降低GC壓力,并且sync.Pool是并發(fā)安全的

2.3 并發(fā)編程

1.鎖的使用

  • 優(yōu)先考慮無鎖的數(shù)據(jù)結(jié)構(gòu)使用,即lock-free 比如atomic包,但其底層也是memory barrier,如果并發(fā)太高,性能也未必高
  • Goroutine局部,即線程的TLS,最后再進行每個Goroutine合并處理
  • 進行數(shù)據(jù)切片,減少鎖的競爭,或者使用讀寫鎖,替換互斥鎖
  • 對于map數(shù)據(jù)結(jié)構(gòu)的并發(fā)訪問,在讀多寫少的情況下,建議使用官方的sync.Map,sync.Map是空間換時間的實現(xiàn)內(nèi)部有兩個map,有一個專門讀的read map,另一個是提供讀寫的dirty map,優(yōu)先讀read map,未讀到會穿透到dirty map,但是不適用于大量寫的場景,dirty map會存在頻繁刷新為read map,整體性能會降低。

2.Goroutine 池化 github.com/panjf2000/ants

到此這篇關(guān)于Go語言中常見的坑以及高性能編程技巧分享的文章就介紹到這了,更多相關(guān)Go編程技巧內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 詳解如何使用Go模塊進行依賴管理

    詳解如何使用Go模塊進行依賴管理

    本文將介紹Go語言中的模塊(module)概念,以及如何使用Go模塊進行依賴管理,我們會探討模塊的基本概念、使用方法、配置和依賴關(guān)系管理等方面的內(nèi)容,需要的朋友可以參考下
    2023-10-10
  • golang jsoniter extension 處理動態(tài)字段的實現(xiàn)方法

    golang jsoniter extension 處理動態(tài)字段的實現(xiàn)方法

    這篇文章主要介紹了golang jsoniter extension 處理動態(tài)字段的實現(xiàn)方法,我們使用實例級別的 extension, 而非全局,可以針對不同業(yè)務(wù)邏輯有所區(qū)分,jsoniter 包提供了比較完善的定制能力,通過例子可以感受一下擴展性,需要的朋友可以參考下
    2023-04-04
  • go+redis實現(xiàn)消息隊列發(fā)布與訂閱的詳細過程

    go+redis實現(xiàn)消息隊列發(fā)布與訂閱的詳細過程

    這篇文章主要介紹了go+redis實現(xiàn)消息隊列發(fā)布與訂閱,redis做消息隊列的缺點:沒有持久化,一旦消息沒有人消費,積累到一定程度后就會丟失,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-09-09
  • Golang極簡入門教程(三):并發(fā)支持

    Golang極簡入門教程(三):并發(fā)支持

    這篇文章主要介紹了Golang極簡入門教程(三):并發(fā)支持,本文講解了goroutine線程、channel 操作符等內(nèi)容,需要的朋友可以參考下
    2014-10-10
  • 學(xué)習(xí)使用Go反射的用法示例

    學(xué)習(xí)使用Go反射的用法示例

    這篇文章主要介紹了學(xué)習(xí)使用Go反射的用法示例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-01-01
  • golang配置文件解析器之goconfig框架的使用詳解

    golang配置文件解析器之goconfig框架的使用詳解

    goconfig是一個易于使用,支持注釋的 Go 語言配置文件解析器,該文件的書寫格式和 Windows 下的 INI 文件一樣,本文主要為大家介紹了goconfig框架的具體使用,需要的可以參考下
    2023-11-11
  • Go內(nèi)存節(jié)省技巧簡單實現(xiàn)方法

    Go內(nèi)存節(jié)省技巧簡單實現(xiàn)方法

    這篇文章主要為大家介紹了Go內(nèi)存節(jié)省技巧簡單實現(xiàn)方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-01-01
  • golang 整合antlr語法校驗解析

    golang 整合antlr語法校驗解析

    Antlr是一個語法分析器,本身是用java實現(xiàn)的,然是Runtime的庫也支持Golang、Java、Python等,本文給大家講解使用golang整合antlr進行語法解析,感興趣的朋友一起看看吧
    2023-02-02
  • Go語言使用goroutine及通道實現(xiàn)并發(fā)詳解

    Go語言使用goroutine及通道實現(xiàn)并發(fā)詳解

    這篇文章主要為大家介紹了Go語言使用goroutine及通道實現(xiàn)并發(fā)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-08-08
  • Go語言開發(fā)發(fā)送Get和Post請求的示例

    Go語言開發(fā)發(fā)送Get和Post請求的示例

    這篇文章主要介紹了Go語言開發(fā)發(fā)送Get和Post請求的示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-07-07

最新評論