<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                defer概述 defer用來聲明一個延遲函數,把這個函數放入到一個棧上,當外部的包含方法return之前,返回參數到調用方法之前調用,也可以說是運行到最外層方法體時調用。我們經常用他來做一些資源的釋放,比如關閉io操作。 defer是golang的一個特色功能,被稱為“延遲調用函數”。當外部函數返回后執行defer。類似于其他語言的 try… catch … finally… 中的finally,當然差別還是明顯的。在使用defer之前我們應該多了解defer的特性,這樣才能避免使用上的誤區。 defer具體規則 1)延遲函數的參數在defer語句出現時就已經確定下來了: ``` package main import "fmt" func f(){ i:=0 defer fmt.Println(i) i++ return } func main(){ f() } ``` 結果: 0 defer語句中的fmt.Println()參數i值在defer出現時就已經確定下來,實際上是拷貝了一份。后面對變量i的修改不會影響fmt.Println()函數的執行,仍然打印“0”. 注意:對于指針類型參數,規則仍然適用,只不過延遲函數的參數是一個地址值,這種情況下,defer后面的語句對變量的修改可能會影響延遲函數。 2)延遲函數執行按后進先出順序執行,即先出現的defer后執行 這個規則很好理解,定義defer類似于入棧操作,執行defer類似于出棧操作。設計defer的初衷是簡化函數返回時資源清理的動作,資源往往有依賴順序,比如先申請資源A,再根據A資源申請B資源,根據B資源申請C資源,即申請順序是:A-->B-->C,釋放時往往又要反向進行。這就是把deffer設計成FIFO的原因。 每申請到一個用完需要釋放的資源時,立即定義一個defer來釋放資源是個很好的習慣。 3)延遲函數可能操作主函數的具名返回值 定義defer的函數,即主函數可能有返回值,返回值沒有名字沒有關系,defer所作用的函數,即延遲函數可能會影響返回值。若要理解延遲函數是如何影響主函數返回值的,只要明白函數是如何返回的就足夠了。 4)函數返回過程 有一個事實必須要了解,關鍵字return不是一個原子操作,實際上return只代理匯編指令ret,即將跳轉程序執行。return i,實際上分兩步進行,即將i值存入棧中作為返回值,然后執行跳轉,而defer的執行時機正是跳轉前,所以說defer執行時還是有機會操作返回值的。 ``` case1: func deferFuncReturn() (result int) { i := 1 defer func() { result++ }() return i } ``` 該函數的return語句可以拆分成下面兩行: ``` result = i return ``` 而延遲函數的執行正是在return之前,即加入defer后的執行過程如下: ``` result = i result++ return ``` 所以上面函數實際返回i++值。 關于上面函數實際返回i++ 關于主函數有不同的返回方式,但返回機制就如上機介紹說,只要把return語句拆開都可以很好的理解,下面分別舉例說明 case2:主函數擁有匿名返回值,返回字面量 一個主函數擁有一個匿名的返回值,返回時使用字面量,比如返回“1”、“2”、“Hello”這樣的值,這種情況下語句時無法操作返回值的。一個返回字面值的函數,如下所示: ``` func foo() int { var i int defer func() { i++ }() return 1 } ``` 上面的return語句,直接把1寫入棧中作為返回值,延遲函數無法操作該返回值,所以就無法影響返回值。 case3:主函數擁有匿名返回值,返回變量 一個主函數擁有一個匿名的返回值,返回使用本地或局部變量,這種情況下,defer語句可以引用到返回值,但不會改變返回值。 ``` func foo() int { var i int defer func() { i++ }() return i } ``` 上面的函數,返回一個局部變量,同時defer函數也會操作這個局部變量。對于匿名返回值來說,可以假定仍然有一個變量存儲返回值,假定返回值變量為“anony”,上面的返回語句可以拆分成一下過程: ``` anony=i i++ return ``` 由于i是整形,會將值拷貝給anony,所以defer語句中修改i值,對函數返回值不造成影響。 case4:主函數擁有具名返回值 主函數聲明語句中帶名字的返回值,會被初始化成一個局部變量,函數內部可以像使用局部變量一樣使用該返回值。如果defer語句操作該返回值,可能會改變返回結果。 一個影響函數返回的例子: ``` func foo() (ret int) { defer func() { ret++ }() return 0 } ``` 上面的函數拆解出來,如下所示: ``` ret = 0 ret++ return ``` 函數真正返回前,在defer中對返回值做了+1操作,所以函數最終返回1 具體題目 題目1: ``` func deferFuncParameter() { var aInt = 1 defer fmt.Println(aInt) aInt = 2 return } ``` 輸出結果:1,延遲函數的參數在defer語句出現的時候就已經確定了,所以無論后面如何修改alnt變量都不會影響延遲函數。 題目2: ``` func main(){ deferFuncParameter() } func printArray(array *[3]int) { for i := range array { fmt.Println(array[i]) } } func deferFuncParameter() { var aArray = [3]int{1, 2, 3} defer printArray(&aArray) aArray[0] = 10 return } ``` 輸出:10 2 3.延遲函數的參數在defer語句出現時就已經確定了,即數組的地址,由于延遲函數執行時機是在return語句之前,所以對數組的最終修改值會被打印出來。 題目3: ``` func main(){ fmt.Println(deferFuncReturn()) } func deferFuncReturn() (result int) { i := 1 defer func() { result++ }() return i } ``` 輸出:2 。函數的return語句并不是原子的,實際執行分為設置返回值-->ret,defer語句實際執行在返回前,即擁有defer的函數返回過程是:設置返回值-->執行defer-->ret.所以return語句先把result設置為i的值,即1,defer語句中又把result遞增1,所以最終返回2. defer的實現原理 1.defer數據結構 ``` type _defer struct { sp uintptr //函數棧指針 pc uintptr //程序計數器 fn *funcval //函數地址 link *_defer //指向自身結構的指針,用于鏈接多個defer } ``` 我們知道defer后面一定要接一個函數的,所以defer的數據結構根一般函數類似,也有棧指針、程序計數器、函數地址等等。 與函數不同的一點是它含有一個指針,可用于指向另一個defer,每個goroutine數據結構中實際上也有一個defer指針,該指針指向一個defer的鏈表,每次聲明一個defer時就將defer插入到單鏈表表頭,每次執行defer就從單鏈表表頭取出一個defer執行。 ![](https://img.kancloud.cn/4e/73/4e73990134d5b4afb4ff6c7974d2de9e_772x471.png) 從上圖可以看到,新聲明的defer總是添加到鏈表頭部。 函數返回前執行defer則是從鏈表首部依次取出執行,不再贅述。 一個goroutine可能連續調用多個函數,defer添加過程跟上述流程一致,進入函數時添加defer,離開函數時取出defer,所以即便調用多個函數,也總是能保證defer是按FIFO方式執行的。 2.defer的創建和執行 源碼包src/runtime/panic.go定義了兩個方法分別用于創建defer和執行defer。 deferproc(): 在聲明defer處調用,將其defer函數存入goroutine的鏈表中; deferreturn(): 在return指令,準確的講是在ret指令前調用,將其defer從goroutine鏈表中取出并執行 可以這么理解,在編譯階段,聲明defer處插入了函數deferproc(),在函數return前插入了函數deferreturn() 3.總結 defer定義的延遲函數參數在defer語句定義時就已經確定下來了; defer定義順序與實際執行順序相反; return不是原子操作,執行過程是:保存返回值(若有)-->執行defer(若有)-->執行ret跳轉 申請資源后立即使用defer關閉資源是好習慣 ## defer 使用中的一些坑 ### 坑1:defer在匿名返回值和命名返回值函數中的不同表現 * 匿名返回,執行 return 語句后,Go會創建一個臨時變量保存返回值,defer修改的是臨時變量,沒有修改返回值。 * 命名返回,執行 return 語句時,并不會再創建臨時變量保存,defer修改的是返回值。 ### 坑2:在for循環中使用defer可能導致的性能問題 ``` func deferInLoops() { for i := 0; i < 100; i++ { f, _ := os.Open("/etc/hosts") defer f.Close() } } ``` defer在緊鄰創建資源的語句后執行,看上去邏輯沒有什么問題,但是和直接調用相比,defer的執行存在著額外的開銷,例如defer會對其后需要的參數進行內存拷貝,還需要對defer結構進行壓棧出棧操作。 ??所以在循環中定義defer可能導致大量的資源開銷,在本例中,可以將f.Close()語句前的defer去掉,來減少大量defer導致的額外資源消耗。 坑3:判斷執行沒有err之后,再defer釋放資源 ??一些獲取資源的操作可能會返回err參數,我們可以選擇忽略返回的err參數,但是如果要使用defer進行延遲釋放的話,需要在使用defer之前先判斷是否存在err,如果資源沒有獲取成功,即沒有必要也不應該再對資源執行釋放操作。如果不判斷獲取資源是否成功就執行釋放操作的話,還有可能導致釋放方法執行錯誤。 正確寫法: ``` resp, err := http.Get(url) // 先判斷操作是否成功 if err != nil { return err } // 如果操作成功,再進行Close操作 defer resp.Body.Close() ``` ### 坑4:調用os.Exit時defer不會被執行 ??當發生panic時,所在goroutine的所有defer會被執行,但是當調用os.Exit()方法退出程序時,defer并不會被執行。 ``` func deferExit() { defer func() { fmt.Println("defer") }() os.Exit(0) } // defer并不會輸出 ``` 坑5:recover 不能跨協程捕獲 panic 信息 recover 必須在 defer 函數中使用,但是不能被 defer 直接調用; 多個 panic 僅有最后一個可以被 recover 捕獲,后面的panic 會覆蓋掉之前的; recover 只能恢復同一個協程中的 panic ,不能跨協程捕獲 panic 信息。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看