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

深度解析Go語言defer機(jī)制

 更新時間:2026年04月29日 10:07:13   作者:初心未改HD  
文章詳細(xì)介紹了Go語言中defer關(guān)鍵字的使用及其特性,涵蓋defer的基本用法、執(zhí)行時機(jī)、LIFO機(jī)制、與返回值的關(guān)系、在panic中的行為、參數(shù)求值時機(jī)、典型應(yīng)用場景及性能考量等,并通過示例代碼幫助理解,感興趣的朋友跟隨小編一起看看吧

前言

defer是Go語言中極具特色的關(guān)鍵字,用于注冊延遲調(diào)用。當(dāng)函數(shù)執(zhí)行到defer語句時,不會立即執(zhí)行被延遲的函數(shù)調(diào)用,而是將調(diào)用壓入一個棧中,在函數(shù)即將返回時(LIFO順序)執(zhí)行。理解defer的執(zhí)行時機(jī)和機(jī)制,對于寫出健壯的Go代碼至關(guān)重要。

一、defer基礎(chǔ)

1.1 defer的基本用法

func before() {
    fmt.Println("before main")
}
?
func after() {
    fmt.Println("after main")
}
?
func main() {
    defer after()
    before()
    fmt.Println("main body")
}

輸出:

before main
main body
after main

1.2 defer的執(zhí)行時機(jī)

defer在return語句之后、函數(shù)退出之前執(zhí)行:

func test() int {
    fmt.Println("1. 函數(shù)體執(zhí)行")
    ret := 0
    defer func() {
        fmt.Println("4. defer執(zhí)行,ret被修改")
        ret = 100
    }()
    fmt.Println("2. defer注冊完畢,繼續(xù)執(zhí)行")
    return ret  // 3. return執(zhí)行,ret=0
}
?
func main() {
    result := test()
    fmt.Printf("5. 最終返回值: %d\n", result)  // 注意:返回值不是100
}

關(guān)鍵發(fā)現(xiàn): defer修改的是命名返回值,但返回的是之前的值副本

1.3 LIFO執(zhí)行順序

多個defer按后進(jìn)先出順序執(zhí)行:

func main() {
    fmt.Println("start")
    defer fmt.Println("defer 1")
    defer fmt.Println("defer 2")
    defer fmt.Println("defer 3")
    fmt.Println("middle")
    defer fmt.Println("defer 4")
    defer fmt.Println("defer 5")
    fmt.Println("end")
}

輸出:

start
middle
end
defer 5      ← LIFO:最后注冊的先執(zhí)行
defer 4
defer 3
defer 2
defer 1

二、defer與返回值

2.1 匿名返回值 vs 命名返回值

匿名返回值:

func匿名() int {
    var result int
    defer func() {
        result = 100
        fmt.Println("defer修改:", result)
    }()
    return result  // 返回0(result的副本)
}
?
func main() {
    fmt.Println("匿名返回值:", 匿名())  // 打印100
}

命名返回值:

func命名() (result int) {
    defer func() {
        result = 100
        fmt.Println("defer修改:", result)
    }()
    return result  // 返回100(與result是同一變量)
}
?
func main() {
    fmt.Println("命名返回值:", 命名())  // 打印100
}

2.2 圖解defer執(zhí)行時機(jī)

return 執(zhí)行過程:
?
    return xxx
       │
       ▼
┌──────────────────┐
│ 1. 計算返回值     │  ← 返回值已確定
├──────────────────┤
│ 2. 調(diào)用defer函數(shù)  │  ← defer在這里執(zhí)行
├──────────────────┤
│ 3. 返回調(diào)用者     │
└──────────────────┘
?
注意:步驟1和步驟2之間,命名返回值已經(jīng)被賦值

三、defer與panic

3.1 defer在panic時的執(zhí)行

func main() {
    fmt.Println("start")
    defer fmt.Println("defer 1")
    defer fmt.Println("defer 2")
    defer fmt.Println("defer 3")
    panic("something went wrong")
    defer fmt.Println("never reached")
}

輸出:

start
defer 3        ← panic前的defer倒序執(zhí)行
defer 2
defer 1
panic: something went wrong

3.2 recover攔截panic

func safeCall(f func()) {
    defer func() {
        if r := recover(); r != nil {
            fmt.Printf("捕獲panic: %v\n", r)
        }
    }()
    f()
}
?
func mayPanic() {
    fmt.Println("mayPanic 開始")
    panic("boom!")
    fmt.Println("mayPanic 結(jié)束")  // 不會執(zhí)行
}
?
func main() {
    fmt.Println("main 開始")
    safeCall(mayPanic)
    fmt.Println("main 繼續(xù)執(zhí)行")
}

輸出:

main 開始
mayPanic 開始
捕獲panic: boom!
main 繼續(xù)執(zhí)行

3.3 defer中panic的傳遞

func main() {
    defer func() {
        fmt.Println("outer defer start")
        defer func() {
            if r := recover(); r != nil {
                fmt.Printf("inner recover: %v\n", r)
            }
        }()
        defer fmt.Println("inner defer")
        panic("inner panic")
    }()
    panic("outer panic")
}

輸出:

inner defer
inner recover: inner panic
outer defer start

分析:

  • outer panic觸發(fā)
  • outer defer開始執(zhí)行,輸出 "outer defer start"
  • 遇到 inner defer,注冊
  • 遇到 inner recover
  • 遇到 inner panic(新的panic)
  • 新的panic觸發(fā),inner recover捕獲 "inner panic"
  • outer defer結(jié)束

四、defer參數(shù)求值時機(jī)

4.1 參數(shù)是立即求值的

func main() {
    i := 0
    defer fmt.Println("defer i =", i)  // 參數(shù)立即求值,i=0
    i = 100
    fmt.Println("main i =", i)  // i=100
}

輸出:

main i = 100
defer i = 0          ← defer注冊時i=0被保存

4.2 閉包捕獲的是變量引用

func main() {
    i := 0
    defer func() {
        fmt.Println("閉包 i =", i)  // 閉包捕獲i的引用
    }()
    i = 100
    fmt.Println("main i =", i)
}

輸出:

main i = 100
閉包 i = 100        ← defer執(zhí)行時,i已經(jīng)是100

4.3 對比分析

func compare() {
    i := 0
    // 方式1:參數(shù)求值
    defer fmt.Println("參數(shù)方式:", i)
    // 方式2:閉包方式
    defer func() {
        fmt.Println("閉包方式:", i)
    }()
    i = 100
}
?
func main() {
    compare()
}

輸出:

閉包方式: 100
參數(shù)方式: 0

五、defer的典型應(yīng)用

5.1 資源釋放

func readFile(filename string) {
    // 打開文件
    file, err := os.Open(filename)
    if err != nil {
        fmt.Printf("打開文件失敗: %v\n", err)
        return
    }
    // 確保關(guān)閉文件
    defer file.Close()
    // 讀取文件內(nèi)容
    data := make([]byte, 1024)
    for {
        n, err := file.Read(data)
        if n == 0 || err != nil {
            break
        }
        fmt.Print(string(data[:n]))
    }
    // defer會在函數(shù)結(jié)束時自動關(guān)閉文件
}

5.2 解鎖Mutex

import "sync"
?
type Counter struct {
    mu    sync.Mutex
    count int
}
?
func (c *Counter) Inc() {
    c.mu.Lock()
    defer c.mu.Unlock()  // 函數(shù)結(jié)束自動解鎖
    c.count++
}
?
func (c *Counter) Get() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.count
}

5.3 釋放數(shù)據(jù)庫連接

type DB struct {
    conn interface{}
}
?
func query(db *DB, sql string) {
    // 獲取連接
    conn := db.getConn()
    defer db.releaseConn(conn)  // 確保釋放
    // 使用連接執(zhí)行查詢
    results := conn.Query(sql)
    // 處理結(jié)果...
    // defer自動釋放連接
}

5.4 打印函數(shù)執(zhí)行時間

func trackExecution(name string) {
    start := time.Now()
    fmt.Printf("開始執(zhí)行 %s...\n", name)
    defer func() {
        elapsed := time.Since(start)
        fmt.Printf("%s 執(zhí)行耗時: %v\n", name, elapsed)
    }()
    // 模擬執(zhí)行
    time.Sleep(100 * time.Millisecond)
}
?
func main() {
    trackExecution("task1")
    trackExecution("task2")
}

5.5 統(tǒng)一錯誤處理

func process() (err error) {
    // 使用命名返回值,確保defer能訪問到err
    defer func() {
        if err != nil {
            fmt.Printf("最終錯誤: %v\n", err)
        }
    }()
    // 步驟1
    if err = step1(); err != nil {
        return fmt.Errorf("step1 failed: %w", err)
    }
    // 步驟2
    if err = step2(); err != nil {
        return fmt.Errorf("step2 failed: %w", err)
    }
    // 步驟3
    if err = step3(); err != nil {
        return fmt.Errorf("step3 failed: %w", err)
    }
    return nil
}
?
func step1() error { return nil }
func step2() error { return errors.New("step2 error") }
func step3() error { return nil }

六、defer的性能

6.1 defer的性能開銷

defer比直接調(diào)用有一定的性能開銷:

import (
    "testing"
)
?
func withoutDefer() {
    // 直接調(diào)用
}
?
func withDefer() {
    defer func() {
        // 空defer
    }()
}
?
func BenchmarkWithoutDefer(b *testing.B) {
    for i := 0; i < b.N; i++ {
        withoutDefer()
    }
}
?
func BenchmarkWithDefer(b *testing.B) {
    for i := 0; i < b.N; i++ {
        withDefer()
    }
}
?
// 典型結(jié)果:
// BenchmarkWithoutDefer    1000000000   0.25 ns/op
// BenchmarkWithDefer       1000000000   35 ns/op
// 
// defer大約有100倍的開銷,但在大多數(shù)場景下可忽略

6.2 何時應(yīng)該避免使用defer

// 場景:循環(huán)中大量創(chuàng)建資源
// 不推薦:每次循環(huán)都注冊defer
func processItems(items []Item) {
    for _, item := range items {
        file, _ := os.Open(item.Path)
        defer file.Close()  // 問題:defer累積,資源無法及時釋放
        // 處理...
    }
}
?
// 推薦:使用代碼塊限制作用域
func processItemsFixed(items []Item) {
    for _, item := range items {
        file, _ := os.Open(item.Path)
        // 處理完后立即關(guān)閉,或使用errgroup批量處理
        file.Close()
    }
}

七、常見面試題

Q1: 下面代碼的輸出是什么?

func main() {
    defer_call()
}
?
func defer_call() {
    defer func() { fmt.Println("1") }()
    defer func() { fmt.Println("2") }()
    defer func() { fmt.Println("3") }()
    panic("panic error")
}

答案:

3
2
1
panic: panic error

Q2: defer的值捕獲問題

func main() {
 ? ?var fs = make([]func(), 3)
 ? ?for i := 0; i < 3; i++ {
 ? ? ? ?fs[i] = func() {
 ? ? ? ? ? ?fmt.Print(i, " ")
 ? ? ?  }
 ?  }
 ? ?for _, f := range fs {
 ? ? ? ?f()
 ?  }
}

答案: 2 2 2(閉包捕獲循環(huán)變量i的引用)

修正:

for i := 0; i < 3; i++ {
 ? ?v := i ?// 創(chuàng)建副本
 ? ?fs[i] = func() {
 ? ? ? ?fmt.Print(v, " ")
 ?  }
}
// 輸出:0 1 2

Q3: defer在return之后的執(zhí)行

func test() (i int) {
 ? ?defer func() { i++ }()
 ? ?return 5
}
?
func main() {
 ? ?fmt.Println(test()) ?// 輸出 6
}

答案: 6。return 5先將i設(shè)為5,然后defer執(zhí)行i++,最終返回6。

總結(jié)

  • 執(zhí)行時機(jī):defer在return之后、函數(shù)退出前執(zhí)行
  • LIFO順序:多個defer按后進(jìn)先出執(zhí)行
  • 參數(shù)求值:defer語句的參數(shù)立即求值,但閉包按引用捕獲
  • panic行為:panic觸發(fā)時,defer仍會執(zhí)行
  • recover:只在defer中調(diào)用才能捕獲panic
  • 性能:有輕微開銷(約35ns),但對大多數(shù)應(yīng)用可忽略

最佳實踐:

  • 總是使用defer釋放資源(文件、連接、鎖)
  • 在循環(huán)中謹(jǐn)慎使用defer
  • defer參數(shù)立即求值,閉包按引用捕獲
  • 命名返回值+defer可以實現(xiàn)靈活的清理邏輯

到此這篇關(guān)于深度解析Go語言defer機(jī)制的文章就介紹到這了,更多相關(guān)Go語言defer機(jī)制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 使用Golang的Context管理上下文的方法

    使用Golang的Context管理上下文的方法

    這篇文章主要介紹了使用Golang的Context管理上下文的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-09-09
  • Golang 統(tǒng)計字符串字?jǐn)?shù)的方法示例

    Golang 統(tǒng)計字符串字?jǐn)?shù)的方法示例

    本篇文章主要介紹了Golang 統(tǒng)計字符串字?jǐn)?shù)的方法示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-05-05
  • go性能分析工具pprof的用途及使用詳解

    go性能分析工具pprof的用途及使用詳解

    剛開始接觸go就遇到了一個內(nèi)存問題,在進(jìn)行內(nèi)存分析的時候發(fā)現(xiàn)了一下比較好的工具,在此留下記錄,下面這篇文章主要給大家介紹了關(guān)于go性能分析工具pprof的用途及使用的相關(guān)資料,需要的朋友可以參考下
    2023-01-01
  • go內(nèi)置函數(shù)copy()的具體使用

    go內(nèi)置函數(shù)copy()的具體使用

    當(dāng)我們在Go語言中需要將一個切片的內(nèi)容復(fù)制到另一個切片時,可以使用內(nèi)置的copy()函數(shù),本文就介紹了go內(nèi)置函數(shù)copy()的具體使用,感興趣的可以了解一下
    2023-08-08
  • golang監(jiān)聽文件變化的實例

    golang監(jiān)聽文件變化的實例

    這篇文章主要介紹了golang監(jiān)聽文件變化的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-03-03
  • Golang操作excel的技巧與方法

    Golang操作excel的技巧與方法

    在Golang中操作Excel可以包括讀取、寫入和編輯Excel文件,你可以定義函數(shù)或方法來執(zhí)行這些操作,本文給大家介紹了Golang操作excel的技巧與方法,文中有詳細(xì)的代碼講解,需要的朋友可以參考下
    2024-05-05
  • 一文帶大家搞懂Go語言中的迭代器

    一文帶大家搞懂Go語言中的迭代器

    迭代器是使用戶可在容器對象上遍訪的對象,設(shè)計人員使用此接口無需關(guān)心容器對象的內(nèi)存分配的實現(xiàn)細(xì)節(jié),本文主要為大家詳細(xì)介紹一下Go語言中的迭代器的實現(xiàn),需要的可以了解下
    2025-02-02
  • go語言中Timer和Ticker兩種計時器的使用

    go語言中Timer和Ticker兩種計時器的使用

    go語言中有Timer和Ticker這樣的兩種計時器,兩種計時器分別實現(xiàn)了不同的計時功能,本文主要介紹了go語言中Timer和Ticker兩種計時器的使用,感興趣的可以了解一下
    2024-08-08
  • go-micro使用Consul做服務(wù)發(fā)現(xiàn)的方法和原理解析

    go-micro使用Consul做服務(wù)發(fā)現(xiàn)的方法和原理解析

    這篇文章主要介紹了go-micro使用Consul做服務(wù)發(fā)現(xiàn)的方法和原理,這里提供一個通過docker快速安裝Consul的方式,當(dāng)然前提是你得安裝了docker,需要的朋友可以參考下
    2022-04-04
  • 優(yōu)雅使用GoFrame共享變量Context示例詳解

    優(yōu)雅使用GoFrame共享變量Context示例詳解

    這篇文章主要為大家介紹了優(yōu)雅使用GoFrame共享變量Context示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06

最新評論

什邡市| 盘山县| 赣榆县| 镇坪县| 东乌珠穆沁旗| 长宁区| 昌黎县| 吉木萨尔县| 邯郸县| 凤翔县| 重庆市| 慈利县| 泰顺县| 南涧| 崇阳县| 公主岭市| 乌恰县| 凤台县| 定兴县| 蒙山县| 东城区| 江油市| 新竹市| 赫章县| 平邑县| 乐亭县| 石台县| 盐亭县| 宜兰县| 翼城县| 上高县| 略阳县| 永泰县| 社旗县| 永仁县| 多伦县| 和龙市| 南川市| 石家庄市| 化德县| 积石山|