延宕執行,妙用無窮,Go lang1.18入門精煉教程,由白丁入鴻儒,Golang中defer關鍵字延遲調用機制使用EP17

来源:https://www.cnblogs.com/v3ucn/archive/2022/09/07/16665066.html
-Advertisement-
Play Games

先行定義,延後執行。不得不佩服Go lang設計者天才的設計,事實上,defer關鍵字就相當於Python中的try{ ...}except{ ...}finally{...}結構設計中的finally語法塊,函數結束時強制執行的代碼邏輯,但是defer在語法結構上更加優雅,在函數退出前統一執行,可 ...


先行定義,延後執行。不得不佩服Go lang設計者天才的設計,事實上,defer關鍵字就相當於Python中的try{ ...}except{ ...}finally{...}結構設計中的finally語法塊,函數結束時強制執行的代碼邏輯,但是defer在語法結構上更加優雅,在函數退出前統一執行,可以隨時增加defer語句,多用於系統資源的釋放以及相關善後工作。當然了,這種流程結構是必須的,形式上可以不同,但底層原理是類似的,Golang 選擇了更簡約的defer,避免多級嵌套的try except finally 結構。

使用場景

操作系統資源在業務上避免不了的,比方說單例對象的使用權、文件讀寫、資料庫讀寫、鎖的獲取和釋放等等,這些資源需要在使用完之後釋放掉或者銷毀,如果忘記釋放、資源會常駐記憶體,長此以往就會造成記憶體泄漏的問題。但是人非聖賢,孰能無過?因此研發者在撰寫業務的時候有幾率忘記關閉這些資源。

Golang中defer關鍵字的優勢在於,在打開資源語句的下一行,就可以直接用defer語句來註冊函數結束後執行關閉資源的操作。說白了就是給程式邏輯“上鬧鐘”,定義好邏輯結束時需要關閉什麼資源,如此,就降低了忘記關閉資源的概率:

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("連接資料庫出錯")  
		return  
	}  
  
	defer db.Close()  
        fmt.Println("鏈接Mysql成功")  
  
}

這裡通過gorm獲取資料庫指針變數後,在業務開始之前就使用defer定義好資料庫鏈接的關閉,在main函數執行完畢之前,執行db.Close()方法,所以列印語句是在defer之前執行的。

所以需要註意的是,defer最好在業務前面定義,如果在業務後面定義:

fmt.Println("鏈接Mysql成功")  
defer db.Close()

這樣寫就是畫蛇添足了,因為本來就是結束前執行,這裡再加個defer關鍵字的意義就不大了,反而會在編譯的時候增加程式的判斷邏輯,得不償失。

defer執行順序問題

Golang並不會限制defer關鍵字的數量,一個函數中允許多個“延遲任務”:

package main  
  
import "fmt"  
  
func main() {  
	defer func1()  
	defer func2()  
	defer func3()  
}  
  
func func1() {  
	fmt.Println("任務1")  
}  
  
func func2() {  
	fmt.Println("任務2")  
}  
  
func func3() {  
	fmt.Println("任務3")  
}

程式返回:

任務3  
任務2  
任務1

我們可以看到,多個defer的執行順序其實是“反”著的,先定義的後執行,後定義的先執行,為什麼?因為defer的執行邏輯其實是一種“壓棧”行為:

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  
}

這裡我們使用切片和結構體實現了棧的數據結構,當元素入棧的時候,會進入棧底,後進的會把先進的壓住,出棧則是後進的先出:

func main() {  
  
	var stack *ItemStack  
	stack = NewStack()  
	stack.Push("任務1")  
	stack.Push("任務2")  
	stack.Push("任務3")  
	fmt.Println(stack.Pop())  
	fmt.Println(stack.Pop())  
	fmt.Println(stack.Pop())  
  
}

程式返回:

任務3  
任務2  
任務1

所以,在defer執行順序中,業務上需要先執行的一定要後定義,而業務上後執行的一定要先定義。

除此以外,就是與其他執行關鍵字的執行順序問題,比方說return關鍵字:

package main  
  
import "fmt"  
  
func main() {  
	test()  
}  
  
func test() string {  
	defer fmt.Println("延時任務執行")  
	return testRet()  
}  
  
func testRet() string {  
	fmt.Println("返回值函數執行")  
	return ""  
}

程式返回:

返回值函數執行  
延時任務執行

一般情況下,我們會認為return就是結束邏輯,所以return邏輯應該會最後執行,但實際上defer會在retrun後面執行,所以defer中的邏輯如果依賴return中的執行結果,那麼就絕對不能使用defer關鍵字。

業務與特性結合

我們知道,有些內置關鍵字不僅僅具備表層含義,如果瞭解其特性,是可以參與業務邏輯的,比如說Python中的try{ ...}except{ ...}finally{...}結構,錶面上是捕獲異常,輸出異常,其實可以利用其特性搭配唯一索引,就可以直接完成排重業務,從而減少一次磁碟的IO操作。

defer也如此,假設我們要在同一個函數中打開不同的文件進行操作:

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()  
}

所以理論上,需要兩個文件句柄對象,分別打開不同的文件,然後同步執行。

但讓defer關鍵字參與進來:

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關閉 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關閉 err err %v\n", err)  
			}  
		}(f)  
	}  
  
	return nil  
}  
  
func main() {  
  
	mergeFile()  
}

這裡就用到了defer的特性,defer函數定義的時候,句柄參數就已經複製進去了,隨後,真正執行close()函數的時候就剛好關閉的是對應的文件了,如此,同一個句柄對不同文件進行了復用,我們就節省了一次記憶體空間的分配。

defer一定會執行嗎

我們知道Python中的try{ ...}except{ ...}finally{...}結構,finally僅僅是理論上會執行,一旦遇到特殊情況:

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

並未執行print("db is opening")邏輯,是因為在__enter__方法中就已經結束了(exit(-1))

而defer同理:

package main  
  
import (  
	"fmt"  
	"os"  
)  
  
func main() {  
	defer func() {  
		fmt.Printf("延後執行")  
	}()  
	os.Exit(1)  
}

這裡和Python一樣,同樣調用os包中的Exit函數,程式返回:

exit status 1

延遲方法並未執行,所以defer並非一定會執行。

結語

defer關鍵字是極其天才的設計,業務簡單的情況下不會有什麼問題。但也需要深入理解defer的特性以及和其他內置關鍵字的關係,才能發揮它最大的威力,著名語言C#最新版本支持了 using無括弧的形式,預設當前塊結束時釋放資源,這也算是對defer關鍵字的一種致敬罷。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.什麼是模板層 模板層可以根據視圖中傳遞的字典數據動態生產相應的HTML頁面 2.模板層的配置 1.在項目下創建一個與同名文件夾平行的templates文件夾 2.在settings.py中的TEMPLATES配置項中 BACKEND:指定模板的引擎 DIRS:模板的搜索目錄(可以是一個或者多個) ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 最近在研究一個基於TP6的框架CRMEB,這裡分享下我的開發心得 首先要獲取原始項目文件 這裡是git地址 https://gitee.com/ZhongBangKeJi/CRMEB.git 項目環境的要求為Apache、MySQL、PH ...
  • 命令版 示例: 將main分支轉到master分支上 切到需要使用的分支 git checkout master 強制忽略歷史融合 git merge main --allow-unrelated-histories 3.提交融合衝突文件即可 ...
  • Django_ajax 1 簡介 AJAX(Asynchronous Javascript And XML)翻譯成中文就是“非同步Javascript和XML”。即使用Javascript語言與伺服器進行非同步交互,傳輸的數據為XML(當然,傳輸的數據不只是XML)。 同步交互:客戶端發出一個請求後,需 ...
  • 作用域 作用域分為: 全局作用域 局部作用域 在函數內部的作用域叫做局部作用域,局部作用域中的變數叫做局部變數 非函數內部的作用域叫做全局作用域,全局作用域中的變數叫做全局變數 局部作用域可以使用全局變數,全局變數不能試用局部變數 變數的使用規則: 從內向外,找到後返回 函數作用域中命名全局變數gl ...
  • 我們上面使用swarm部署服務,單個服務還好,如果很多個服務怎麼來解決呢,這裡就用到了Docker Stack管理服務。 ​ 在上面我們學會瞭如何配置一個swarm集群,並且知道如何在swarm集群上部署應用,現在,我們開始瞭解Docker層級關係中的最高一個層級——stack。一個stack就是一 ...
  • “如何解決TCC中的懸掛問題”! 一個工作了4年的Java程式員,去京東面試,被問到這個問題。 大家好,我是Mic,一個工作了14年的Java程式員 這個問題面試官想考察什麼方面的知識?我們又該怎麼回答呢? 問題解析 TCC是分散式事務問題裡面的解決方案,一般在應聘互聯網公司的時候問的比較多。 實際 ...
  • 1.什麼是視圖層 簡單來說,就是用來接收路由層傳來的請求,從而做出相應的響應返回給瀏覽器 2.視圖層的格式與參數說明 2.1基本格式 from django.http import HttpResponse def page_2003(request): html = '<h1>第一個網頁</h1> ...
一周排行
    -Advertisement-
    Play Games
  • 基於.NET Framework 4.8 開發的深度學習模型部署測試平臺,提供了YOLO框架的主流系列模型,包括YOLOv8~v9,以及其系列下的Det、Seg、Pose、Obb、Cls等應用場景,同時支持圖像與視頻檢測。模型部署引擎使用的是OpenVINO™、TensorRT、ONNX runti... ...
  • 十年沉澱,重啟開發之路 十年前,我沉浸在開發的海洋中,每日與代碼為伍,與演算法共舞。那時的我,滿懷激情,對技術的追求近乎狂熱。然而,隨著歲月的流逝,生活的忙碌逐漸占據了我的大部分時間,讓我無暇顧及技術的沉澱與積累。 十年間,我經歷了職業生涯的起伏和變遷。從初出茅廬的菜鳥到逐漸嶄露頭角的開發者,我見證了 ...
  • C# 是一種簡單、現代、面向對象和類型安全的編程語言。.NET 是由 Microsoft 創建的開發平臺,平臺包含了語言規範、工具、運行,支持開發各種應用,如Web、移動、桌面等。.NET框架有多個實現,如.NET Framework、.NET Core(及後續的.NET 5+版本),以及社區版本M... ...
  • 前言 本文介紹瞭如何使用三菱提供的MX Component插件實現對三菱PLC軟元件數據的讀寫,記錄了使用電腦模擬,模擬PLC,直至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1. PLC開發編程環境GX Works2,GX Works2下載鏈接 https:// ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • 1、jQuery介紹 jQuery是什麼 jQuery是一個快速、簡潔的JavaScript框架,是繼Prototype之後又一個優秀的JavaScript代碼庫(或JavaScript框架)。jQuery設計的宗旨是“write Less,Do More”,即倡導寫更少的代碼,做更多的事情。它封裝 ...
  • 前言 之前的文章把js引擎(aardio封裝庫) 微軟開源的js引擎(ChakraCore))寫好了,這篇文章整點js代碼來測一下bug。測試網站:https://fanyi.youdao.com/index.html#/ 逆向思路 逆向思路可以看有道翻譯js逆向(MD5加密,AES加密)附完整源碼 ...
  • 引言 現代的操作系統(Windows,Linux,Mac OS)等都可以同時打開多個軟體(任務),這些軟體在我們的感知上是同時運行的,例如我們可以一邊瀏覽網頁,一邊聽音樂。而CPU執行代碼同一時間只能執行一條,但即使我們的電腦是單核CPU也可以同時運行多個任務,如下圖所示,這是因為我們的 CPU 的 ...
  • 掌握使用Python進行文本英文統計的基本方法,並瞭解如何進一步優化和擴展這些方法,以應對更複雜的文本分析任務。 ...
  • 背景 Redis多數據源常見的場景: 分區數據處理:當數據量增長時,單個Redis實例可能無法處理所有的數據。通過使用多個Redis數據源,可以將數據分區存儲在不同的實例中,使得數據處理更加高效。 多租戶應用程式:對於多租戶應用程式,每個租戶可以擁有自己的Redis數據源,以確保數據隔離和安全性。 ...