最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

 更新時(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ǔ)言的特性,以及自己在寫Go項(xiàng)目中做的總結(jié),從Go常見的數(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"

一. 常見的坑

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

1.在函數(shù)調(diào)用過(guò)程中,數(shù)組是值傳遞pass-by-value,必要時(shí)必須使用slice。因?yàn)閟lice是引用類型,在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ù)展開的問(wèn)題

func main() {
    var a = []interface{}{111, 222, 333}
    fmt.Println(a)
    fmt.Println(a...)
}
// 不管是否展開,編譯器都會(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之間不滿足順序一致性的內(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, 也可以讀到之前存入的值,讀取完畢后開始讀零值,已經(jīng)關(guān)閉的channel寫入則會(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)體類型struct{}時(shí),且Item中包含一些比較大內(nèi)存的成員類型,比如 [2048]byte,如果每次遍歷[]struct{} ,都會(huì)進(jìn)行一次值拷貝,所以會(huì)帶來(lái)性能消耗。此外,因?yàn)?range 遍歷事獲取值拷貝的副本,所以對(duì)副本的修改,是不會(huì)影響到原切片。 如果切片的Item是指針類型,即[]*struct{} 則兩者遍歷方法都一樣

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

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

不過(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)有任何成員類型字段

盡量少使用反射,因?yàn)榉瓷淅镞厾砍兜筋愋团袛嗪蛢?nèi)存分配,會(huì)對(duì)性能產(chǎn)生影響

2.2 內(nèi)存管理

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

值傳遞會(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的開銷增大,影響性能。

一般情況下,對(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),或者使用讀寫鎖,替換互斥鎖
  • 對(duì)于map數(shù)據(jù)結(jié)構(gòu)的并發(fā)訪問(wèn),在讀多寫少的情況下,建議使用官方的sync.Map,sync.Map是空間換時(shí)間的實(shí)現(xiàn)內(nèi)部有兩個(gè)map,有一個(gè)專門讀的read map,另一個(gè)是提供讀寫的dirty map,優(yōu)先讀read map,未讀到會(huì)穿透到dirty map,但是不適用于大量寫的場(chǎng)景,dirty map會(huì)存在頻繁刷新為read map,整體性能會(huì)降低。

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

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

相關(guān)文章

  • Golang學(xué)習(xí)筆記(二):類型、變量、常量

    Golang學(xué)習(xí)筆記(二):類型、變量、常量

    這篇文章主要介紹了Golang學(xué)習(xí)筆記(二):類型、變量、常量,本文講解了基本類型、保留字、變量、常量、枚舉、運(yùn)算符、指針、分組聲明等內(nèi)容,需要的朋友可以參考下
    2015-05-05
  • Go語(yǔ)言基礎(chǔ)變量的聲明及初始化示例詳解

    Go語(yǔ)言基礎(chǔ)變量的聲明及初始化示例詳解

    這篇文章主要為大家介紹了Go語(yǔ)言基礎(chǔ)變量的聲明及初始化示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助祝大家多多進(jìn)步,早日升職加薪
    2021-11-11
  • Go語(yǔ)言學(xué)習(xí)之將mp4通過(guò)rtmp推送流媒體服務(wù)的實(shí)現(xiàn)方法

    Go語(yǔ)言學(xué)習(xí)之將mp4通過(guò)rtmp推送流媒體服務(wù)的實(shí)現(xiàn)方法

    對(duì)音視頻一直是小白,決定沉下心來(lái),好好研究一下音視頻知識(shí),下面這篇文章主要給大家介紹了關(guān)于Go語(yǔ)言學(xué)習(xí)之將mp4通過(guò)rtmp推送流媒體服務(wù)的實(shí)現(xiàn)方法,需要的朋友可以參考下
    2022-12-12
  • Go語(yǔ)言dolphinscheduler任務(wù)調(diào)度處理

    Go語(yǔ)言dolphinscheduler任務(wù)調(diào)度處理

    這篇文章主要為大家介紹了Go語(yǔ)言dolphinscheduler任務(wù)調(diào)度處理,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • Go?Excelize?API源碼解析GetSheetFormatPr使用示例

    Go?Excelize?API源碼解析GetSheetFormatPr使用示例

    這篇文章主要為大家介紹了Go?Excelize?API源碼解析GetSheetFormatPr使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • go與go mod命令使用方式以及遇到的問(wèn)題

    go與go mod命令使用方式以及遇到的問(wèn)題

    這篇文章主要介紹了go與go mod命令使用方式以及遇到的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-09-09
  • Go語(yǔ)言中的內(nèi)存布局詳解

    Go語(yǔ)言中的內(nèi)存布局詳解

    這篇文章主要給大家介紹了Go語(yǔ)言中的內(nèi)存布局,那么本文中將嘗試解釋Go如何在內(nèi)存中構(gòu)建結(jié)構(gòu)體,以及結(jié)構(gòu)體在字節(jié)和比特位方面是什么樣子。 有需要的朋友們可以參考借鑒,感興趣的朋友們下面來(lái)跟著小編一起學(xué)習(xí)學(xué)習(xí)吧。
    2016-11-11
  • GoLang?channel關(guān)閉狀態(tài)相關(guān)操作詳解

    GoLang?channel關(guān)閉狀態(tài)相關(guān)操作詳解

    Channel?和?goroutine?的結(jié)合是?Go?并發(fā)編程的大殺器。而?Channel?的實(shí)際應(yīng)用也經(jīng)常讓人眼前一亮,通過(guò)與?select,cancel,timer?等結(jié)合,它能實(shí)現(xiàn)各種各樣的功能。接下來(lái),我們就要介紹GoLang?channel關(guān)閉狀態(tài)相關(guān)操作
    2022-10-10
  • Go 協(xié)程超時(shí)控制的實(shí)現(xiàn)

    Go 協(xié)程超時(shí)控制的實(shí)現(xiàn)

    本文主要介紹了Go 協(xié)程超時(shí)控制的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-08-08
  • gin?框架下使用URL重定向

    gin?框架下使用URL重定向

    本文主要介紹了gin?框架下使用URL重定向,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2026-05-05

最新評(píng)論

乌鲁木齐市| 肥乡县| 汽车| 静宁县| 峨眉山市| 若尔盖县| 南昌县| 吉首市| 洮南市| 巴东县| 黄陵县| 巴林左旗| 青冈县| 珠海市| 夹江县| 罗江县| 永登县| 林甸县| 木兰县| 三明市| 桂平市| 清水县| 奎屯市| 淳化县| 巴南区| 崇阳县| 宁城县| 怀柔区| 长白| 大荔县| 天全县| 嵊泗县| 梨树县| 沙田区| 丰顺县| 通榆县| 澎湖县| 云南省| 新干县| 彭州市| 木兰县|