十年網(wǎng)站開發(fā)經(jīng)驗(yàn) + 多家企業(yè)客戶 + 靠譜的建站團(tuán)隊(duì)
量身定制 + 運(yùn)營維護(hù)+專業(yè)推廣+無憂售后,網(wǎng)站問題一站解決
先行定義,延后執(zhí)行。不得不佩服Go lang設(shè)計(jì)者天才的設(shè)計(jì),事實(shí)上,defer關(guān)鍵字就相當(dāng)于Python中的try{ ...}except{ ...}finally{...}結(jié)構(gòu)設(shè)計(jì)中的finally語法塊,函數(shù)結(jié)束時(shí)強(qiáng)制執(zhí)行的代碼邏輯,但是defer在語法結(jié)構(gòu)上更加優(yōu)雅,在函數(shù)退出前統(tǒng)一執(zhí)行,可以隨時(shí)增加defer語句,多用于系統(tǒng)資源的釋放以及相關(guān)善后工作。當(dāng)然了,這種流程結(jié)構(gòu)是必須的,形式上可以不同,但底層原理是類似的,Golang 選擇了更簡約的defer,避免多級(jí)嵌套的try except finally 結(jié)構(gòu)。

成都創(chuàng)新互聯(lián)專注為客戶提供全方位的互聯(lián)網(wǎng)綜合服務(wù),包含不限于成都網(wǎng)站建設(shè)、成都網(wǎng)站設(shè)計(jì)、通渭網(wǎng)絡(luò)推廣、微信平臺(tái)小程序開發(fā)、通渭網(wǎng)絡(luò)營銷、通渭企業(yè)策劃、通渭品牌公關(guān)、搜索引擎seo、人物專訪、企業(yè)宣傳片、企業(yè)代運(yùn)營等,從售前售中售后,我們都將竭誠為您服務(wù),您的肯定,是我們最大的嘉獎(jiǎng);成都創(chuàng)新互聯(lián)為所有大學(xué)生創(chuàng)業(yè)者提供通渭建站搭建服務(wù),24小時(shí)服務(wù)熱線:18982081108,官方網(wǎng)址:www.cdcxhl.com
操作系統(tǒng)資源在業(yè)務(wù)上避免不了的,比方說單例對(duì)象的使用權(quán)、文件讀寫、數(shù)據(jù)庫讀寫、鎖的獲取和釋放等等,這些資源需要在使用完之后釋放掉或者銷毀,如果忘記釋放、資源會(huì)常駐內(nèi)存,長此以往就會(huì)造成內(nèi)存泄漏的問題。但是人非圣賢,孰能無過?因此研發(fā)者在撰寫業(yè)務(wù)的時(shí)候有幾率忘記關(guān)閉這些資源。
Golang中defer關(guān)鍵字的優(yōu)勢(shì)在于,在打開資源語句的下一行,就可以直接用defer語句來注冊(cè)函數(shù)結(jié)束后執(zhí)行關(guān)閉資源的操作。說白了就是給程序邏輯“上鬧鐘”,定義好邏輯結(jié)束時(shí)需要關(guān)閉什么資源,如此,就降低了忘記關(guān)閉資源的概率:
package main
import (
"fmt"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
)
func main() {
db, err := gorm.Open("mysql", "root:root@(localhost)/mytest?charset=utf8mb4&parseTime=True&loc=Local")
if err != nil {
fmt.Println(err)
fmt.Println("連接數(shù)據(jù)庫出錯(cuò)")
return
}
defer db.Close()
fmt.Println("鏈接Mysql成功")
}
這里通過gorm獲取數(shù)據(jù)庫指針變量后,在業(yè)務(wù)開始之前就使用defer定義好數(shù)據(jù)庫鏈接的關(guān)閉,在main函數(shù)執(zhí)行完畢之前,執(zhí)行db.Close()方法,所以打印語句是在defer之前執(zhí)行的。
所以需要注意的是,defer最好在業(yè)務(wù)前面定義,如果在業(yè)務(wù)后面定義:
fmt.Println("鏈接Mysql成功")
defer db.Close()
這樣寫就是畫蛇添足了,因?yàn)楸緛砭褪墙Y(jié)束前執(zhí)行,這里再加個(gè)defer關(guān)鍵字的意義就不大了,反而會(huì)在編譯的時(shí)候增加程序的判斷邏輯,得不償失。
Golang并不會(huì)限制defer關(guān)鍵字的數(shù)量,一個(gè)函數(shù)中允許多個(gè)“延遲任務(wù)”:
package main
import "fmt"
func main() {
defer func1()
defer func2()
defer func3()
}
func func1() {
fmt.Println("任務(wù)1")
}
func func2() {
fmt.Println("任務(wù)2")
}
func func3() {
fmt.Println("任務(wù)3")
}
程序返回:
任務(wù)3
任務(wù)2
任務(wù)1
我們可以看到,多個(gè)defer的執(zhí)行順序其實(shí)是“反”著的,先定義的后執(zhí)行,后定義的先執(zhí)行,為什么?因?yàn)閐efer的執(zhí)行邏輯其實(shí)是一種“壓?!毙袨椋?/p>
package main
import (
"fmt"
"sync"
)
// Item the type of the stack
type Item interface{}
// ItemStack the stack of Items
type ItemStack struct {
items []Item
lock sync.RWMutex
}
// New creates a new ItemStack
func NewStack() *ItemStack {
s := &ItemStack{}
s.items = []Item{}
return s
}
// Pirnt prints all the elements
func (s *ItemStack) Print() {
fmt.Println(s.items)
}
// Push adds an Item to the top of the stack
func (s *ItemStack) Push(t Item) {
s.lock.Lock()
s.lock.Unlock()
s.items = append(s.items, t)
}
// Pop removes an Item from the top of the stack
func (s *ItemStack) Pop() Item {
s.lock.Lock()
defer s.lock.Unlock()
if len(s.items) == 0 {
return nil
}
item := s.items[len(s.items)-1]
s.items = s.items[0 : len(s.items)-1]
return item
}
這里我們使用切片和結(jié)構(gòu)體實(shí)現(xiàn)了棧的數(shù)據(jù)結(jié)構(gòu),當(dāng)元素入棧的時(shí)候,會(huì)進(jìn)入棧底,后進(jìn)的會(huì)把先進(jìn)的壓住,出棧則是后進(jìn)的先出:
func main() {
var stack *ItemStack
stack = NewStack()
stack.Push("任務(wù)1")
stack.Push("任務(wù)2")
stack.Push("任務(wù)3")
fmt.Println(stack.Pop())
fmt.Println(stack.Pop())
fmt.Println(stack.Pop())
}
程序返回:
任務(wù)3
任務(wù)2
任務(wù)1
所以,在defer執(zhí)行順序中,業(yè)務(wù)上需要先執(zhí)行的一定要后定義,而業(yè)務(wù)上后執(zhí)行的一定要先定義。
除此以外,就是與其他執(zhí)行關(guān)鍵字的執(zhí)行順序問題,比方說return關(guān)鍵字:
package main
import "fmt"
func main() {
test()
}
func test() string {
defer fmt.Println("延時(shí)任務(wù)執(zhí)行")
return testRet()
}
func testRet() string {
fmt.Println("返回值函數(shù)執(zhí)行")
return ""
}
程序返回:
返回值函數(shù)執(zhí)行
延時(shí)任務(wù)執(zhí)行
一般情況下,我們會(huì)認(rèn)為return就是結(jié)束邏輯,所以return邏輯應(yīng)該會(huì)最后執(zhí)行,但實(shí)際上defer會(huì)在retrun后面執(zhí)行,所以defer中的邏輯如果依賴return中的執(zhí)行結(jié)果,那么就絕對(duì)不能使用defer關(guān)鍵字。
我們知道,有些內(nèi)置關(guān)鍵字不僅僅具備表層含義,如果了解其特性,是可以參與業(yè)務(wù)邏輯的,比如說Python中的try{ ...}except{ ...}finally{...}結(jié)構(gòu),表面上是捕獲異常,輸出異常,其實(shí)可以利用其特性搭配唯一索引,就可以直接完成排重業(yè)務(wù),從而減少一次磁盤的IO操作。
defer也如此,假設(shè)我們要在同一個(gè)函數(shù)中打開不同的文件進(jìn)行操作:
package main
import (
"os"
)
func mergeFile() error {
f1, _ := os.Open("file1.txt")
if f1 != nil {
//操作文件
f1.Close()
}
f2, _ := os.Open("file2.txt")
if f2 != nil {
//操作文件
f2.Close()
}
return nil
}
func main(){
mergeFile()
}
所以理論上,需要兩個(gè)文件句柄對(duì)象,分別打開不同的文件,然后同步執(zhí)行。
但讓defer關(guān)鍵字參與進(jìn)來:
package main
import (
"fmt"
"io"
"os"
)
func mergeFile() error {
f, _ := os.Open("file1.txt")
if f != nil {
defer func(f io.Closer) {
if err := f.Close(); err != nil {
fmt.Printf("文件1關(guān)閉 err %v\n", err)
}
}(f)
}
f, _ = os.Open("file2.txt")
if f != nil {
defer func(f io.Closer) {
if err := f.Close(); err != nil {
fmt.Printf("文件2關(guān)閉 err err %v\n", err)
}
}(f)
}
return nil
}
func main() {
mergeFile()
}
這里就用到了defer的特性,defer函數(shù)定義的時(shí)候,句柄參數(shù)就已經(jīng)復(fù)制進(jìn)去了,隨后,真正執(zhí)行close()函數(shù)的時(shí)候就剛好關(guān)閉的是對(duì)應(yīng)的文件了,如此,同一個(gè)句柄對(duì)不同文件進(jìn)行了復(fù)用,我們就節(jié)省了一次內(nèi)存空間的分配。
我們知道Python中的try{ ...}except{ ...}finally{...}結(jié)構(gòu),finally僅僅是理論上會(huì)執(zhí)行,一旦遇到特殊情況:
from peewee import MySQLDatabase
class Db:
def __init__(self):
self.db = MySQLDatabase('mytest', user='root', password='root',host='localhost', port=3306)
def __enter__(self):
print("connect")
self.db.connect()
exit(-1)
def __exit__(self,*args):
print("close")
self.db.close()
with Db() as db:
print("db is opening")
程序返回:
connect
并未執(zhí)行print("db is opening")邏輯,是因?yàn)樵赺_enter__方法中就已經(jīng)結(jié)束了(exit(-1))
而defer同理:
package main
import (
"fmt"
"os"
)
func main() {
defer func() {
fmt.Printf("延后執(zhí)行")
}()
os.Exit(1)
}
這里和Python一樣,同樣調(diào)用os包中的Exit函數(shù),程序返回:
exit status 1
延遲方法并未執(zhí)行,所以defer并非一定會(huì)執(zhí)行。
defer關(guān)鍵字是極其天才的設(shè)計(jì),業(yè)務(wù)簡單的情況下不會(huì)有什么問題。但也需要深入理解defer的特性以及和其他內(nèi)置關(guān)鍵字的關(guān)系,才能發(fā)揮它最大的威力,著名語言C#最新版本支持了 using無括號(hào)的形式,默認(rèn)當(dāng)前塊結(jié)束時(shí)釋放資源,這也算是對(duì)defer關(guān)鍵字的一種致敬罷。