上一篇 Go聖經-學習筆記之函數值(二)golang
形參數量可變的函數稱爲可變參數函數。使用最多的可變參數函數標準庫:fmt
。網絡
在聲明可變參數函數時,須要在參數列表的最後一個參數類型以前加上省略符號...
。函數
func 函數名(t Type1, t Type2, tn ...TypeN) 返回參數列表 {} // DEMO func sum(x, y ...int) int { count:=x for _, elem:= range y { count+=elem } return count } fmt.Println(sum(1,2,3,4,5,6,7,8)) // 使用方法1 elems:=[]int{2,3,4,5,6,7,8} fmt.Println(sum(1, elems...) // 使用方法2
defer表示函數退出時,棧內的函數列表退棧執行。這個主要介紹defer和return之間的關係。源碼分析
func Incr() (i int) { defer func(){ i++ }() return 1 } fmt.Println(Incr()) // 打印值i = 1 ?
實際上defer和return的執行順序是沒有什麼問題的。先執行defer,而後再return。重點是 return xx。前面章節說過,它不是一個原子操做。分解動做爲:學習
func Incr() (i int) { defer func() { i++ }() i=1 return }
第二個是我在寫Go服務端時的一個小經驗,首先說一下背景,咱們通常想作日誌追蹤或者請求鏈的調用追蹤,看看程序執行到哪一個步驟而後出錯了。這時,咱們通常會這樣作:.net
func AddSaleOrder(so *SaleOrder, o *orm.Ormer) (retCode int, err error) { ...... Logger.Info("[%d] enter AddSaleOrder.", so.SaleOrderId) defer Logger.Info("[%d] left AddSaleOrder.", so.SaleOrderId) ...... return }
可是爲了懶不想寫兩句,或者節約代碼長度什麼的。而後就採用了寫出了下面這種方式:指針
func bigSlowOp() { defer TraceLog("bigSlowOp")() fmt.Println("hello,world") time.Sleep(3 * time.Second) return } func TraceLog(methodName string, params ...interface{}) func() { start := time.Now() fmt.Printf("[%v] enter %s\n", params, methodName) return func() { fmt.Printf("[time: %s | %v] left %s\n", time.Since(start), params, methodName) } } func main() { bigSlowOp() return } // 輸出結果 /* [[]] enter bigSlowOp hello,world [time: 3.000241212s | []] left bigSlowOp */
那麼,咱們之後只須要寫defer TraceLog(方法名,參數列表)()
就OK了日誌
defer語句常常用於處理成對的操做,如打開、關閉、鏈接、斷開鏈接、加鎖、釋放鎖等,不管函數是正常執行完成,仍是提早退出或者panic,defer堆棧裏的函數列表都會執行。那麼資源也能夠釋放。code
同時,咱們也要注意引入defer,若是不注意,可能會帶來資源泄露問題:
for _, filename:= range filenames{ f, err := os.Open(filename) if err !=nil{ return } defer f.Close() // 由於f的生命週期一直在for循環內,因此defer在退出以前不會執行。若是文件列表過大,就是形成文件描述符大量泄露,若是是網絡,則會出現"connection peer by peer"的相關錯誤。 } // 解決方案:使f變量的生命週期縮短,和for循環的執行週期分離開 func doFile(filename string) error { f, err := os.Open(filename) if err !=nil{ return err } defer f.Close() return nil } for _, filename:= range filenames { err := doFile(filename) }
一旦有生命週期的障礙,首先想到函數體執行的生命週期。
通常而言程序在運行過程當中,發生了數組越界,空指針引用錯誤等,這些錯誤會引發panic異常。當發生panic時,程序會中斷運行,並當即在Goroutine中被延遲的函數。隨後並輸出日誌信息,包括髮生錯誤值,以及上下文調用棧。
可是並非全部的panic都來自運行時,開發者也能夠直接調用panic函數引起panic異常;panic接收任何參數。爲了方便診斷問題,標準庫runtime容許開發者輸出堆棧信息。例如:
func main(){ defer printStack() f(3) } func printStack() { var buf [4096]byte runtime.Stack(buf[:], false) os.Stdout.Write(buf[:n]) }
一般來講,不該該對panic異常作任何處理。讓異常儘可能早暴露早修復,提升軟件的可用性。但有時也須要從異常恢復,至少讓程序崩潰前作一些操做,好比: 若是網絡服務端掛掉,若是服務端不作任何處理,直接panic,則客戶端遲遲得不到服務端的響應,影響用戶體驗。
若是在defer內調用了內置函數recover,而且定義該defer的函數發生了panic,recover會使程序從panic恢復,並返回panic value。致使panic異常的函數不會再運行,但正常返回。在未發生panic時,調用recover會返回nil。
這裏要說明一下,至於panic被哪裏的recover捕獲,或者不會捕獲,能夠參考這篇文章: panic和recover工做原理
後續我會經過雨痕老師的Go源碼分析書籍,深刻分析panic和recover,這樣更容易深刻地理解。