Go 的 defer 的特性還是有必要要了解下的!!!

2021-02-13 奇伢雲存儲




Golang 的 defer 是什麼?通俗來講就是延遲調用。defer 會在當前函數返回之前執行 defer 註冊的函數。比如 defer func_x( )  這樣語句會讓你註冊一個函數變量到 defer 的全局鍊表中,在 defer 語句所在的函數退出之前調用。

筆者使用一段時間 Golang 之後,對 Golang defer 的理解認為作用有兩點:

panic 場景依然會被調用:這個是重要的一個特性,通常能簡化我們的代碼,確保無論任何場景,defer 的函數一定調用,通常用在鎖或者資源的釋放場景較多;配套的兩個行為代碼可以放在最近的位置:創建&釋放、加鎖&放鎖、前置&後置,使得代碼更易讀,編程體驗優秀。最近的地方是哪裡?下一行;

先看下 defer 的以下幾個特性:



我們先深入的剖析下 defer 具有的特性,知其然也。這些特性是需要我們記住的特點,才能更好的理解 defer 使用的場景。


package main

func main() {
 defer println("--- defer ---")
 println("--- end ---")
}

defer 會在 main 函數 return 之前時候調用。核心要點:

延遲調用:defer 語句本身雖然是 main 的第一行,但是 fmt.Println 是先列印的;defer 關鍵字一定是處於函數上下文:defer 必須放在函數內部;

一個函數中有多個 defer 調用怎麼辦?壓棧式執行,後入先出。

package main

import (
    "strconv"
)

func main() {
 for i := 1; i <= 6; i++ {
  defer println("defer -->" + strconv.Itoa(i))
 }
 println("--- end ---")
}

壓棧式執行,也就是說先註冊的函數後調用。如上,我們註冊的順序式 1,2,3,4,5,6,最後列印 "--- end ---",所以執行的結果自然是反著來的,程序輸出:

--- end ---
defer -->6
defer -->5
defer -->4
defer -->3
defer -->2
defer -->1

要點:defer 和函數綁定。 兩個理解,defer  只會和 defer  語句所在的特定函數綁定在一起,作用域也只在這個函數。從語法上來講,defer  語句也一定要在函數內,否則會報告語法錯誤。

package main

func main() {
 func() {
  defer println("--- defer ---")
 }()
 println("--- ending ---")
}

如上,defer 處於一個匿名函數中,就 main 函數本身來講,匿名函數 fun(){}() 先調用且返回,然後再調用 println("--- ending ---") ,所以程序輸出自然是:

--- defer ---
--- ending ---

這個是個非常重要的特性:panic 也能執行。Golang 不鼓勵異常的編程模式,但是卻也留了 panic-recover 這個異常和捕捉異常的機制。所以 defer 機制就顯得尤為重要,甚至可以說是必不可少的。因為你沒有一個無視異常,永保調用的 defer 機制,很有可能就會發生各種資源洩露,死鎖等場景。為什麼?因為發生了 panic 卻不代表進程一定會掛掉,很有可能被外層 recover 住。

package main

func main() {
 defer func() {
  if e := recover(); e != nil {
   println("--- defer ---")
  }
 }()
 panic("throw panic")
}

如上,main 函數註冊一個 defer ,且稍後主動觸發 panic,main 函數退出之際就會調用 defer 註冊的匿名函數。再提一點,這裡其實有兩個要點:

defer 在 panic 異常場景也能確保調用;recover 必須和 defer 結合才有意義;


以下的例子對兩個並發的協程做了下同步控制,常規操作。

var wg sync.WaitGroup

for i := 0; i < 2; i++ {
    wg.Add(1)
    go func() {
        defer wg.Done()
        // 程序邏輯
    }()
}
wg.Wait()

加鎖解鎖必須配套,在 Golang 有了 defer 之後,你就可以寫了 lock 之後,立馬就寫 unlock ,這樣就永遠不會忘了。

 mu.RLock()
 defer mu.RUnlock()

但是請注意,lock 以下的代碼都會在鎖內。所以下面的代碼要足夠精簡和快速才行,如果說下面的邏輯很複雜,那麼可能就需要手動控制 unlock 防止的位置了。

某些資源是臨時創建的,作用域只存在於現場函數中,用完之後需要銷毀,這種場景也適用 defer 來釋放。釋放就在創建的下一行,這是個非常好的編程體驗,這種編程方式能極大的避免資源洩漏。因為寫了創建立馬就可以寫釋放了,再也不會忘記了。

    // new 一個客戶端 client;
    cli, err := clientv3.New(clientv3.Config{Endpoints: endpoints})
    if err != nil {
        log.Fatal(err)
    }
    // 釋放該 client ,也就是說該 client 的聲明周期就只在該函數中;
    defer cli.Close()

recover 必須和 defer 結合才行,使用姿勢一般如下:

 defer func() {
  if v := recover(); v != nil {
   _ = fmt.Errorf("PANIC=%v", v)
  }
 }()



defer 其實並不是 Golang 獨創,是多種高級語言的共同選擇;defer 最重要的一個特點就是無視異常可執行,這個是 Golang 在提供了 panic-recover 機制之後必須做的補償機制;defer 的作用域存在於函數,defer 也只有和函數結合才有意義;defer 允許你把配套的兩個行為代碼放在最近相鄰的兩行,比如創建&釋放、加鎖&放鎖、前置&後置,使得代碼更易讀,編程體驗優秀;

本篇從 defer 的使用姿勢入手,了解 defer 的特性,讓大家知其然也。後續會從源碼和實現的角度出發,梳理下 defer ,然後知其所以然也。

相關焦點

  • 深入理解 Go 語言 defer
    (f1()) println(f2()) println(f3())}1. return 語句在解析上面的題目之前,要理解一個前提是 Go 的函數返回值是通過堆棧返回的,這也是實現了多返回值的方法。
  • Go 語言之 defer 的前世今生
    延遲語句 defer 在最早期的 Go 語言設計中並不存在,後來才單獨增加了這一特性,由 Robert Griesemer 完成語言規範的編寫 [Griesemer, 2009], 並由 Ken Thompson 完成最早期的實現 [Thompson, 2009],兩人合作完成這一語言特性。
  • Go 語言之 defer 的前世今生 - CSDN
    作者 | 歐長坤來源 | 碼農桃花源延遲語句 defer 在最早期的 Go 語言設計中並不存在,後來才單獨增加了這一特性,由 Robert Griesemer 完成語言規範的編寫 [Griesemer, 2009], 並由 Ken Thompson 完成最早期的實現 [Thompson, 2009],兩人合作完成這一語言特性。
  • Go語言defer你不知道的事
    設計 defer 的初衷是簡化函數返回時資源清理的動作,資源往往有依賴順序,比如先申請 A 資源,再根據 A 資源申請 B 資源,根據 B 資源申請 C 資源,即申請順序是:A-->B-->C,釋放時往往又要反向進行。這就是把 defer 設計成 FIFO 的原因。
  • go 學習筆記之解讀什麼是defer延遲函數
    Go 語言中有個 defer 關鍵字,常用於實現延遲函數來保證關鍵代碼的最終執行,常言道: "未雨綢繆方可有備無患".「雪之夢技術驛站」: 上述代碼邏輯還是清晰簡單的,可能不會忘記釋放資源也能保證操作順序,但是如果邏輯代碼比較複雜的情況,這時候就有一定的實現難度了!可能是為了簡化類似代碼的邏輯,Go 語言引入了 defer 關鍵字,創造了"延遲函數"的概念.
  • Go 經典入門系列 28:Defer
    什麼是 defer? defer 語句的用途是:含有 defer 語句的函數,會在該函數將要返回之前,調用另一個函數。這個定義可能看起來很複雜,我們通過一個示例就很容易明白了。運行該程序,你會看到有如下輸出:Started finding largestLargest number in [78 109 2 563 300] is 563Finished finding largestlargest 函數開始執行後,會列印上面的兩行輸出
  • 解密 defer 原理,究竟背著程序猿做了多少事情?
    (Go 的 defer 的特性還是有必要要了解下的!!!)調用都會對應到一個 _defer 結構體,一個函數內可以有多個 defer 調用,所以自然需要一個數據結構來組織這些 _defer 結構體。defer 的性能,號稱針對 defer 場景提升了 30% 的性能。
  • golang中defer的執行過程詳解
    defer是go的關鍵字。本文通過go彙編信息,深入分析了defer的調用棧原理在同一個goroutine中:多個defer的調用棧原理是什麼?defer函數是如何調用的?string) { fmt.Println(x, arg)}func bbb(arg string) { fmt.Println(arg)}輸出:從輸出結果看很像棧的數據結構特性:後進先出(LIFO)。
  • 『GCTT 出品』Go 中 defer 的 5 個坑 - 第一部分
    #2 — 在循環中使用 defer切忌在循環中使用 defer,除非你清楚自己在做什麼,因為它們的執行結果常常會出人意料。但是,在某些情況下,在循環中使用 defer 會相當方便,例如將函數中的遞歸轉交給 defer,但這顯然已經不是本文應該講解的內容。
  • 深入剖析 defer 原理篇 —— 函數調用的原理?
    16 個字節要注意,就是 caller 函數 rbp 的保存值和 caller 下一行要執行的指令地址。golang 語言層面函數返回對應了 return 關鍵字,這個有必要深入理解下。兩個寄存器是棧幀的最重要的兩個寄存器,這兩個值劃定了棧幀;rbp 寄存器的常見的作用棧基寄存器,但其實再深入了解下你會知道 rbp 在當今體系裡其實可以作為通用寄存器了。
  • 60分鐘快速了解Go語言
    Go擁有命令式語言的靜態類型,編譯很快,執行也很快,同時加入了對於目前多核CPU的並發計算支持,也有相應的特性來實現大規模編程。Go語言有非常棒的標準庫,還有一個充滿熱情的社區。// 單行注釋/* 多行注釋 */// 導入包的子句在每個源文件的開頭。
  • Golang中defer的實現原理
    數據結構我們先來看下defer結構體src/src/runtime/runtime2.go:_defertype _defer struct { siz     int32 //defer函數的參數大小 started bool //是否被調用,默認為
  • 坑爹代碼 | Go 語言的 defer 能製造出多少坑來?
    Go 語言的 defer 語句是一個非常有用的特性,可以將一個方法延遲到包裹該方法的方法返回時執行
  • 使用 Panic、Defer 和 Recover 處理 Go 錯誤
    via:https://medium.com/technofunnel/error-handling-in-golang-with-panic-defer-and-recover-d77db7ae3875作者:Mayank Gupta四哥水平有限,如有翻譯或理解錯誤,煩請幫忙指出,感謝!文章源自 Medium,點讚超過 700+。
  • Defer還是不Defer?這一屆留學生的靈魂考題怎麼答?
    這也是今年秋季入學的同學們最為關注和糾結的問題—— 到底要不要延期入學呢?,如果同學們想要GAP一年,很有可能會需要重新申請。 弊端五:因為疫情,全球的經濟在大環境下肯定會不太理想,從而導致就業環境變得不好,那麼選擇出國或者考研的人數都會變多,整體的競爭會更加激勵。
  • 詳解defer實現機制(附上三道面試題,我不信你們都能做對)
    今天與大家來聊一聊go中的關鍵字defer,目前很多程式語言中都有defer關鍵字,而go語言的defer用於資源的釋放,會在函數返回之前進行調用,它會經常被用於關閉文件描述符、關閉資料庫連接以及解鎖資源。下面我們就深入Go語言源碼介紹defer關鍵字的實現原理。
  • 學習Async,Defer 和動態腳本,本文就夠了!
    但是這個世界上仍然有很多地區的人們所使用的網絡速度很慢,並且使用的是遠非完美的移動網際網路連接。幸運的是,這裡有兩個 <script> 特性(attribute)可以為我們解決這個問題:defer 和 async。 deferdefer 特性告訴瀏覽器不要等待腳本。相反,瀏覽器將繼續處理 HTML,構建 DOM。
  • Golang中的Defer必掌握的7知識點
    >知識點5:defer遇見panic知識點6:defer中包含panic知識點7:defer下的函數參數包含子函數多個defer出現的時候,它是一個「棧」的關係,也就是先進後出。,defer後的語句後執行該知識點不屬於defer本身,但是調用的場景卻與defer有聯繫,所以也算是defer必備了解的知識點之一。
  • golang的defer使用相關
    ()) fmt.Println("-") deferFunc3() fmt.Println("-") deferFunc4()}func deferFunc1() { i := 1 defer fmt.Print(i) i = 2 return}func deferFunc2() (result int) { i := 1 defer func() { result
  • 上網課還是defer?英國院校政策信息匯總
    ​隨著9月份臨近,英國也要準備開學了,很多留學生都在猶豫要不要返校。關於9月英國大學入學的同學仍是要堅持放鬆的心境,畢竟本年因疫情導致申請量下降的話,同學們的競爭壓力也會變小!Defer就是英國大學關於拿到offer的學生,給予延期一年入學的政策,但條件是在拿到offer的狀況下。相比較空一年或許下一年再申請,推遲一年入學既保證了下一年有學上,又可以避開疫情的影響。英國的高校也在積極制定相關的法規,以下是英國高校的網課政策以及是否允許學生申請defer,快來看看吧!