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

Go語(yǔ)言中常見(jiàn)的坑以及高性能編程技巧分享

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

背景

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

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

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

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

一. 常見(jiàn)的坑

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

1.在函數(shù)調(diào)用過(guò)程中,數(shù)組是值傳遞pass-by-value,必要時(shí)必須使用slice。因?yàn)閟lice是引用類(lèi)型,在slice傳參時(shí),只會(huì)復(fù)制slice的Data指針和len、cap,形參和實(shí)參的slice使用的是同一個(gè)底層數(shù)組。

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

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

錯(cuò)誤示例:

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.當(dāng)函數(shù)的可變參數(shù)是空接口時(shí),傳入空接口的切片時(shí)需要注意參數(shù)展開(kāi)的問(wèn)題

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

5.對(duì)于切片的操作,會(huì)操作同一個(gè)底層數(shù)組,因此對(duì)于一個(gè)切片的修改操作,會(huì)影響到整個(gè)數(shù)組。另外由于string 在go中是immutable,對(duì)于同一個(gè)字符串的兩個(gè)變量,Go做了內(nèi)存優(yōu)化,會(huì)使用的相同的底層數(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語(yǔ)言特性相關(guān)

1.Go中的recover捕獲的是祖父級(jí)調(diào)用時(shí)候的panic,直接調(diào)用時(shí)不會(huì)有任何效果,必須在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之間不滿(mǎn)足順序一致性的內(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.閉包的錯(cuò)誤使用,導(dǎo)致使用同一個(gè)變量

錯(cuò)誤示例:

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

錯(cuò)誤示例:

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語(yǔ)言是帶內(nèi)存自動(dòng)回收的特性,因此內(nèi)存一般不會(huì)泄漏。但是Goroutine卻存在泄漏的情況,同時(shí)泄漏的Goroutine引用的內(nèi)存也同樣無(wú)法被回收。因此可通過(guò)context包或者退出的channel來(lái)避免Goroutine的泄漏。

錯(cuò)誤示例:

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
}

上面的程序中后臺(tái)Goroutine向channel輸入整數(shù),main函數(shù)中輸出該整數(shù)。但是當(dāng)值為10時(shí), break跳出for循環(huán),后臺(tái)Goroutine就處于無(wú)法被回收的狀態(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()  //通過(guò)調(diào)用cancel()來(lái)通知后臺(tái)Goroutine退出
            // stopChan <- struct{}{}
            break
        }
    }
    // do some thing
}

6. go channel注意事項(xiàng)

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

二. 高性能Go編程

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

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

遍歷 []struct{} 使用下標(biāo)而不是 range

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

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

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

可參見(jiàn):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
}

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

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

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

盡量少使用反射,因?yàn)榉瓷淅镞厾砍兜筋?lèi)型判斷和內(nèi)存分配,會(huì)對(duì)性能產(chǎn)生影響

2.2 內(nèi)存管理

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

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

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

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

2.3 并發(fā)編程

1.鎖的使用

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

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

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

相關(guān)文章

  • 詳解如何使用Go模塊進(jìn)行依賴(lài)管理

    詳解如何使用Go模塊進(jìn)行依賴(lài)管理

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

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

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

    go+redis實(shí)現(xiàn)消息隊(duì)列發(fā)布與訂閱的詳細(xì)過(guò)程

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

    Golang極簡(jiǎn)入門(mén)教程(三):并發(fā)支持

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

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

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

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

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

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

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

    golang 整合antlr語(yǔ)法校驗(yàn)解析

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

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

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

    Go語(yǔ)言開(kāi)發(fā)發(fā)送Get和Post請(qǐng)求的示例

    這篇文章主要介紹了Go語(yǔ)言開(kāi)發(fā)發(fā)送Get和Post請(qǐng)求的示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-07-07

最新評(píng)論